咨询珠峰架构课请加微信
Gruntfile
文件正确配置好了任务,任务运行器就会自动帮你或你的小组完成大部分无聊的工作。npm run build
steam
流打包npm run build
npm run build
npm run start
debug.js
const webpack = require("webpack");
const config = require("./webpack.config.js"); //1.读取配置文件
debugger;
const compiler = webpack(config);
function compilerCallback(err, stats) {
const statsString = stats.toString();
console.log(statsString);
}
debugger
compiler.run((err, stats) => {
compilerCallback(err, stats);
});
加载器
。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力插件
。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果
const path = require("path");
const RunPlugin = require("./plugins/RunPlugin");
const DonePlugin = require("./plugins/DonePlugin");
module.exports = {
context: process.cwd(),
mode: "development",
devtool: false,
entry: "./src/app.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
},
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
include: path.join(__dirname, "src"),
exclude: /node_modules/,
},
],
},
plugins: [new RunPlugin(),new DonePlugin()],
devServer: {},
};
flow.js
let fs = require('fs');
let path = require('path');
const { SyncHook } = require("tapable");
class Compiler {
constructor(options) {
this.options = options;
this.hooks = {
run: new SyncHook(),
done: new SyncHook(),
};
}
run() {
this.hooks.run.call();
let modules = [];
let chunks = [];
let files = [];
// 确定入口:根据配置中的entry找出所有的入口文件
let entry = path.join(this.options.context, this.options.entry);
//从入口文件出发,调用所有配置的Loader对模块进行编译,
let entryContent = fs.readFileSync(entry, "utf8");
let entrySource = babelLoader(entryContent);
let entryModule = { id: entry, source: entrySource };
modules.push(entryModule);
//再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
let title = path.join(this.options.context, "./src/title.js");
let titleContent = fs.readFileSync(title, "utf8");
let titleSource = babelLoader(titleContent);
let titleModule = { id: title, source: titleSource };
modules.push(titleModule);
//根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
let chunk = { name: "main", modules };
chunks.push(chunk);
//再把每个Chunk转换成一个单独的文件加入到输出列表
let file = {
file: this.options.output.filename,
source: `
(function (modules) {
function __webpack_require__(moduleId) {
var module = { exports: {} };
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
return module.exports;
}
return __webpack_require__("./src/app.js");
})(
{
"./src/app.js": function (module, exports, __webpack_require__) {
var title = __webpack_require__("./src/title.js");
console.log(title);
},
"./src/title.js": function (module) {
module.exports = "title";
},
});
`,
};
files.push(file);
//在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
let outputPath = path.join(
this.options.output.path,
this.options.output.filename
);
fs.writeFileSync(outputPath, file.source,'utf8');
this.hooks.done.call();
}
}
//1.从配置文件和Shell语句中读取与合并参数,得出最终的参数
let options = require('./webpack.config');
//2.用上一步得到的参数初始化Compiler对象
let compiler = new Compiler(options);
//3.加载所有配置的插件
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler);
}
}
//4.执行对象的run方法开始执行编译
compiler.run();
function babelLoader(source) {
return `var sum = function sum(a, b) {
return a + b;
};`;
}
plugins\RunPlugin.js
module.exports = class RunPlugin {
apply(compiler) {
compiler.hooks.run.tap("RunPlugin", () => {
console.log("RunPlugin");
});
}
}
plugins\DonePlugin.js
module.exports = class DonePlugin {
apply(compiler) {
compiler.hooks.done.tap("DonePlugin", () => {
console.log("DonePlugin");
});
}
};
loader | 解决问题 |
---|---|
babel-loader | 把 ES6 或React转换成 ES5 |
css-loader | 加载 CSS,支持模块化、压缩、文件导入等特性 |
eslint-loader | 通过 ESLint 检查 JavaScript 代码 |
file-loader | 把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 |
url-loader | 和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去 |
sass-loader | 把Sass/SCSS文件编译成CSS |
postcss-loader | 使用PostCSS处理CSS |
css-loader | 主要来处理background:(url)还有@import这些语法。让webpack能够正确的对其路径进行模块化处理 |
style-loader | 把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。 |
插件 | 解决问题 |
---|---|
case-sensitive-paths-webpack-plugin | 如果路径有误则直接报错 |
terser-webpack-plugin | 使用terser来压缩JavaScript |
pnp-webpack-plugin | Yarn Plug'n'Play插件 |
html-webpack-plugin | 自动生成带有入口文件引用的index.html |
webpack-manifest-plugin | 生产资产的显示清单文件 |
optimize-css-assets-webpack-plugin | 用于优化或者压缩CSS资源 |
mini-css-extract-plugin | 将CSS提取为独立的文件的插件,对每个包含css的js文件都会创建一个CSS文件,支持按需加载css和sourceMap |
ModuleScopePlugin | 如果引用了src目录外的文件报警插件 |
InterpolateHtmlPlugin | 和HtmlWebpackPlugin串行使用,允许在index.html中添加变量 |
ModuleNotFoundPlugin | 找不到模块的时候提供一些更详细的上下文信息 |
DefinePlugin | 创建一个在编译时可配置的全局常量,如果你自定义了一个全局变量PRODUCTION,可在此设置其值来区分开发还是生产环境 |
HotModuleReplacementPlugin | 启用模块热替换 |
WatchMissingNodeModulesPlugin | :此插件允许你安装库后自动重新构建打包文件 |
'use strict';
const webpack = require('webpack');
const paths = require("./paths");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PnpWebpackPlugin = require("pnp-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");
const getClientEnvironment = require("./env");
const cssRegex = /\.css$/;
const sassRegex = /\.(scss|sass)$/;
module.exports = function (webpackEnv) {
console.log("webpackEnv", webpackEnv); //webpackEnv development
//开发环境
const isEnvDevelopment = webpackEnv === "development";
//生产环境
const isEnvProduction = webpackEnv === "production";
//set GENERATE_SOURCEMAP=false
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";
console.log("shouldInlineRuntimeChunk", shouldInlineRuntimeChunk);
// %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
//忽略结束的/ 把环境变量中的变量注入到当前应用中来
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
console.log("env", env);
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve("style-loader"),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
},
{
loader: require.resolve("css-loader"),
options: cssOptions,
},
{
loader: require.resolve("postcss-loader"),
},
].filter(Boolean);
if (preProcessor) {
loaders.push(
{
loader: require.resolve("resolve-url-loader")
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
);
}
return loaders;
};
return {
mode: isEnvProduction ? "production" : "development",
devtool: isEnvProduction
? shouldUseSourceMap
? "source-map"
: false
: isEnvDevelopment && "cheap-module-source-map",
entry: [
isEnvDevelopment &&
require.resolve("react-dev-utils/webpackHotDevClient"),
paths.appIndexJs,
].filter(Boolean),
output: {
path: isEnvProduction ? paths.appBuild : undefined, //输出的目标路径
//一个main bundle一个文件,每个异步代码块也对应一个文件 ,在生产环境中,并不产出真正的文件
filename: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: "static/js/bundle.js",
//如果使用了代码分割的话,这里有额外的JS代码块文件
chunkFilename: isEnvProduction
? "static/js/[name].[contenthash:8].chunk.js"
: "static/js/[name].chunk.js",
//打后后的文件的访问路径
publicPath: paths.publicUrlOrPath,
},
optimization: {
minimize: isEnvProduction,
minimizer: [
//压缩JS
new TerserPlugin({}),
//压缩CSS
new OptimizeCSSAssetsPlugin({}),
],
//自动分割第三方模块和公共模块
splitChunks: {
chunks: "all",
name: false,
},
//为了长期缓存保持运行时代码块是单独的文件
runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`,
},
},
resolve: {
//设置modules的目录
modules: ["node_modules", paths.appNodeModules],
//指定扩展名
extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`),
alias: {
//设置别名
"react-native": "react-native-web",
},
plugins: [
PnpWebpackPlugin,
//防止用户引用在src或者node_modules之外的文件
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
module: {
rules: [
//在babel处理之前执行linter
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: "pre",
use: [
{
loader: require.resolve("eslint-loader"),
},
],
include: paths.appSrc,
},
{
//OneOf会遍历接下来的loader直到找一个匹配要求的,如果没有匹配的会走file-loader
oneOf: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
},
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
},
{
test: /\.(js|mjs)$/,
loader: require.resolve("babel-loader"),
},
{
test: cssRegex,
//用于配置css-loader,作用于@import资源之前有多少个loader
//0=>无(默认) 1=>postcss-loader 2 postcss-loader sass-loader
use: getStyleLoaders({ importLoaders: 1 }),
},
{
test: sassRegex,
//postcss-loader sass-loader
use: getStyleLoaders({ importLoaders: 3 }, "sass-loader"),
},
{
loader: require.resolve("file-loader"),
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: "static/media/[name].[hash:8].[ext]",
},
},
],
},
],
},
plugins: [
//使用插入的script标签生成一个index.html标签
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
//保证在index.html中获取到环境变量public URL可以通过%PUBLIC_URL%获取
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
//模块找不到的时候提供一些必要上下文信息
new ModuleNotFoundPlugin(paths.appPath),
//保证在JS中获取到环境变量if (process.env.NODE_ENV === 'production') { ... }
new webpack.DefinePlugin(env.stringified),
//模块热更新插件
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
//当你大小写拼错的时候进行提示
isEnvDevelopment && new CaseSensitivePathsPlugin(),
//重新安装模块后不用重新启动开发服务器
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
//提取CSS
isEnvProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
}),
//生成一个manifest文件
new ManifestPlugin({
fileName: "asset-manifest.json",
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
(fileName) => !fileName.endsWith(".map")
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
].filter(Boolean),
};
};
paths.js
'use strict';
const path = require('path');
const fs = require('fs');
//当前的工作目录
const appDirectory = fs.realpathSync(process.cwd());
//从相对路径中解析绝对路径
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
//获取PublicUrlOrPath
const publicUrlOrPath = require(resolveApp("package.json")).homepage || process.env.PUBLIC_URL || "";
//默认的模块扩展名
const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];
//解析模块路径
const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);
if (extension) {
return resolveFn(`${filePath}.${extension}`);
}
return resolveFn(`${filePath}.js`);
};
module.exports = {
dotenv: resolveApp('.env'),//客户端环境变量的文件名路径
appPath: resolveApp('.'),//当前工作路径
appBuild: resolveApp('build'),//输出的build目标路径
appPublic: resolveApp('public'),//public目录
appHtml: resolveApp('public/index.html'),//html文件绝对路径
appIndexJs: resolveModule(resolveApp, 'src/index'),//入口文件
appPackageJson: resolveApp('package.json'),//package.json文件路径
appSrc: resolveApp('src'),//src路径
appTsConfig: resolveApp('tsconfig.json'),
appJsConfig: resolveApp('jsconfig.json'),
appNodeModules: resolveApp('node_modules'),
publicUrlOrPath,
};
module.exports.moduleFileExtensions = moduleFileExtensions;
env.js
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
//一般可能是production或者development set NODE_ENV=development
const NODE_ENV = process.env.NODE_ENV;
//环境变量的文件路径
const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local
`${paths.dotenv}.${NODE_ENV}`, // .env.development
//在测试环境下不要包括.env.local
NODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.local
paths.dotenv,//.env
].filter(Boolean);
//从.env*文件中加载环境变量
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
//支持通过NODE_PATH加载解析模块 set NODE_PATH=modules;extraModules
const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter);
//获取NODE_ENV and REACT_APP_*环境变量,并且准备通过DefinePlugin插入应用
//set REACT_APP_NAME=zhufeng
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter((key) => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
//决定当前是否处于开发模式
NODE_ENV: process.env.NODE_ENV || "development",
//用来解析处于public下面的正确资源路径
PUBLIC_URL: publicUrl,
}
);
//把所有的值转成字符串以便在DefinePlugin中使用
const stringified = {
"process.env": Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;
'use strict';
const fs = require('fs');
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
const paths = require('./paths');
const host = process.env.HOST || '0.0.0.0';
module.exports = function(proxy, allowedHost) {
return {
//禁用主机检查
disableHostCheck: true,
//启动gzip压缩
compress: true,
//禁用WebpackDevServer自己的日志,警告和错误还是可以显示的
clientLogLevel: "none",
//静态文件根目录
contentBase: paths.appPublic,
contentBasePublicPath: paths.publicUrlOrPath,
//默认情况下contentBase里的文件变更不会触发页面刷新
watchContentBase: true,
//启用热更新
hot: true,
//使用ws而非socketjs-node模块
transportMode: "ws",
//不需要注入WS客户端
injectClient: false,
//访问路径
publicPath: paths.publicUrlOrPath.slice(0, -1),
//更少的WebpackDevServer日志
quiet: true,
watchOptions: {
ignored: ignoredFiles(paths.appSrc), //不要监控src目录
},
host,
historyApiFallback: {
//禁用dotRule
disableDotRule: true,
index: paths.publicUrlOrPath,
},
public: allowedHost,
//proxy会在before和after之间执行
proxy,
before(app, server) {
//在出错的时候获取源码内容
app.use(evalSourceMapMiddleware(server));
//让我们从运行时错误打开文件
app.use(errorOverlayMiddleware());
//由于代理注册的中间件
if (fs.existsSync(paths.proxySetup)) {
require(paths.proxySetup)(app);
}
},
after(app) {
//如果URL不匹配重定向到`PUBLIC_URL` or `homepage` from `package.json`
app.use(redirectServedPath(paths.publicUrlOrPath));
},
};
};
webpack.dev.config.js
const { merge } = require("webpack-merge");
let config = require('./webpack.config');
let devServerConfig = require('./webpackDevServer.config');
module.exports = merge(config('development'), {
devServer: devServerConfig()
});
postcss.config.js
module.exports = {
plugins: [
require("autoprefixer")({ overrideBrowserslist: ["> 0.15% in CN"] }), // 自动添加css前缀
],
};
{
"env": {
"browser": true,
"es2020": true
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {
}
}
{
"scripts": {
"build": "webpack --env=production",
"start": "webpack-dev-server --env=development --config webpack.dev.config.js"
}
}
source maps
文件,map
文件是一种对应编译文件和源文件的方法类型 | 含义 |
---|---|
source-map | 原始代码 最好的sourcemap质量有完整的结果,但是会很慢 |
eval-source-map | 原始代码 同样道理,但是最高的质量和最低的性能 |
cheap-module-eval-source-map | 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能 |
cheap-eval-source-map | 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl |
eval | 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap |
cheap-source-map | 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用 |
cheap-module-source-map | 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射 |
看似配置项很多, 其实只是五个关键字eval、source-map、cheap、module和inline的任意组合
关键字 | 含义 |
---|---|
eval | 使用eval包裹模块代码 |
source-map | 产生.map文件 |
cheap | 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap |
module | 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件 |
inline | 将.map作为DataURI嵌入,不单独生成.map文件 |
cnpm i react react-dom -S
cnpm install webpack webpack-cli webpack-dev-server image-webpack-loader mini-css-extract-plugin purgecss-webpack-plugin babel-loader @babel/core @babel/preset-env @babel/preset-react terser-webpack-plugin html-webpack-plugin optimize-css-assets-webpack-plugin mini-css-extract-plugin qiniu -D
optimization: {
minimize: true,
minimizer: [
//压缩JS
+ new TerserPlugin({})
]
},
optimization: {
minimize: true,
minimizer: [
//压缩CSS
+ new OptimizeCSSAssetsPlugin({}),
]
},
{
test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
use: [
"file-loader",
{
+ loader: "image-webpack-loader",
+ options: {
+ mozjpeg: {
+ progressive: true,
+ quality: 65,
+ },
+ optipng: {
+ enabled: false,
+ },
+ pngquant: {
+ quality: "65-90",
+ speed: 4,
+ },
+ gifsicle: {
+ interlaced: false,
+ },
+ webp: {
+ quality: 75,
+ }
+ }
+ }
]
}
const path = require("path");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const PurgecssPlugin = require("purgecss-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
+ loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
}
]
},
plugins: [
+ new MiniCssExtractPlugin({
+ filename: "[name].css",
+ }),
+ new PurgecssPlugin({
+ paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
+ })
]
devServer: {},
};
module.exports = {
+ mode:'production',
+ devtool:false,
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
{
loader: "babel-loader",
options: {
+ presets: [["@babel/preset-env", { "modules": false }]],
},
},
],
}
}
}
production
下默认开启,开发环境要用 webpack.optimize.ModuleConcatenationPlugin
插件hello.js
export default 'Hello';
index.js
import str from './hello.js';
console.log(str);
main.js
var hello = ('hello');
console.log(hello);
entry: {
index: "./src/index.js",
login: "./src/login.js"
}
hello.js
module.exports = "hello";
index.js
document.querySelector('#clickBtn').addEventListener('click',() => {
import('./hello').then(result => {
console.log(result.default);
});
});
index.html
<button id="clickBtn">点我</button>
index.js
import React, { Component, Suspense } from "react";
import ReactDOM from "react-dom";
import Loading from "./components/Loading";
/* function lazy(loadFunction) {
return class LazyComponent extends React.Component {
state = { Comp: null };
componentDidMount() {
loadFunction().then((result) => {
this.setState({ Comp: result.default });
});
}
render() {
let Comp = this.state.Comp;
return Comp ? <Comp {...this.props} /> : null;
}
};
} */
const AppTitle = React.lazy(() =>
import(/* webpackChunkName: "title" */ "./components/Title")
);
class App extends Component {
constructor(){
super();
this.state = {visible:false};
}
show(){
this.setState({ visible: true });
};
render() {
return (
<>
{this.state.visible && (
<Suspense fallback={<Loading />}>
<AppTitle />
</Suspense>
)}
<button onClick={this.show.bind(this)}>加载</button>
</>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
src\components\Loading.js
import React, { Component, Suspense } from "react";
export default (props) => {
return <p>Loading</p>;
};
src\components\Title.js
import React, { Component, Suspense } from "react";
export default props=>{
return <p>Title</p>;
}
Low
<link rel="preload" as="script" href="utils.js">
import(
`./utils.js`
/* webpackPreload: true */
/* webpackChunkName: "utils" */
)
<link rel="prefetch" href="utils.js" as="script">
button.addEventListener('click', () => {
import(
`./utils.js`
/* webpackPrefetch: true */
/* webpackChunkName: "utils" */
).then(result => {
result.default.log('hello');
})
});
webpack.config.js
entry: {
page1: "./src/page1.js",
page2: "./src/page2.js",
page3: "./src/page3.js",
},
optimization: {
splitChunks: {
chunks: "all", //默认作用于异步chunk,值为all/initial/async
minSize: 0, //默认值是30kb,代码块的最小尺寸
minChunks: 1, //被多少模块共享,在分割之前模块的被引用次数
maxAsyncRequests: 2, //限制异步模块内部的并行最大请求数的,说白了你可以理解为是每个import()它里面的最大并行请求数量
maxInitialRequests: 4, //限制入口的拆分数量
name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~
automaticNameDelimiter: "~", //默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js'
cacheGroups: {
//设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
vendors: {
chunks: "all",
test: /node_modules/, //条件
priority: -10, ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
},
commons: {
chunks: "all",
minSize: 0, //最小提取字节数
minChunks: 2, //最少被几个chunk引用
priority: -20
}
}
}
src\page1.js
import utils1 from "./module1";
import utils2 from "./module2";
import $ from "jquery";
console.log(utils1, utils2, $);
import(/* webpackChunkName: "asyncModule1" */ "./asyncModule1");
src\page2.js
import utils1 from "./module1";
import utils2 from "./module2";
import $ from "jquery";
console.log(utils1, utils2, $);
src\page3.js
import utils1 from "./module1";
import utils3 from "./module3";
import $ from "jquery";
console.log(utils1, utils3, $);
src\module1.js
console.log("module1");
src\module2.js
console.log("module2");
src\module3.js
console.log("module3");
src\asyncModule1.js
import _ from 'lodash';
console.log(_);
Asset Size Chunks Chunk Names
asyncModule1.chunk.js 740 bytes asyncModule1 [emitted] asyncModule1
index.html 498 bytes [emitted]
page1.js 10.6 KiB page1 [emitted] page1
page1~page2.chunk.js 302 bytes page1~page2 [emitted] page1~page2
page1~page2~page3.chunk.js 308 bytes page1~page2~page3 [emitted] page1~page2~page3
page2.js 7.52 KiB page2 [emitted] page2
page3.js 7.72 KiB page3 [emitted] page3
vendors~asyncModule1.chunk.js 532 KiB vendors~asyncModule1 [emitted] vendors~asyncModule1
vendors~page1~page2~page3.chunk.js 282 KiB vendors~page1~page2~page3 [emitted] vendors~page1~page2~page3
Entrypoint page1 = vendors~page1~page2~page3.chunk.js page1~page2~page3.chunk.js page1~page2.chunk.js page1.js
Entrypoint page2 = vendors~page1~page2~page3.chunk.js page1~page2~page3.chunk.js page1~page2.chunk.js page2.js
Entrypoint page3 = vendors~page1~page2~page3.chunk.js page1~page2~page3.chunk.js page3.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const UploadPlugin = require("./plugins/UploadPlugin");
const glob = require("glob");
const PATHS = {
src: path.join(__dirname, "src"),
};
module.exports = {
mode: "development",
devtool: false,
context: process.cwd(),
entry: {
main: "./src/index.js",
},
output: {
path: path.resolve(__dirname, "dist"),
+ filename: "[name].[hash].js",
+ chunkFilename: "[name].[hash].chunk.js",
+ publicPath: "http://img.zhufengpeixun.cn/",
},
optimization: {
minimize: true,
minimizer: [
//压缩JS
/* new TerserPlugin({
sourceMap: false,
extractComments: false,
}),
//压缩CSS
new OptimizeCSSAssetsPlugin({}), */
],
//自动分割第三方模块和公共模块
splitChunks: {
chunks: "all", //默认作用于异步chunk,值为all/initial/async
minSize: 0, //默认值是30kb,代码块的最小尺寸
minChunks: 1, //被多少模块共享,在分割之前模块的被引用次数
maxAsyncRequests: 2, //限制异步模块内部的并行最大请求数的,说白了你可以理解为是每个import()它里面的最大并行请求数量
maxInitialRequests: 4, //限制入口的拆分数量
name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~
automaticNameDelimiter: "~", //默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js'
cacheGroups: {
//设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
vendors: {
chunks: "all",
test: /node_modules/, //条件
priority: -10, ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
},
commons: {
chunks: "all",
minSize: 0, //最小提取字节数
minChunks: 2, //最少被几个chunk引用
priority: -20,
reuseExistingChunk: true, //如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
},
},
},
//为了长期缓存保持运行时代码块是单独的文件
/* runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`,
}, */
},
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
{
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", { modules: false }],
"@babel/preset-react",
],
},
},
],
},
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
{
test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
use: [
"file-loader",
{
loader: "image-webpack-loader",
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
optipng: {
enabled: false,
},
pngquant: {
quality: "65-90",
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75,
},
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
+ filename: "[name].[hash].css",
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
new UploadPlugin({}),
],
devServer: {},
};
const qiniu = require("qiniu");
const path = require("path");
//https://developer.qiniu.com/kodo/sdk/1289/nodejs
require("dotenv").config();
const defaultAccessKey = process.env.accessKey;
const defaultSecretKey = process.env.secretKey;
class UploadPlugin {
constructor(options) {
this.options = options || {};
}
apply(compiler) {
compiler.hooks.afterEmit.tap("UploadPlugin", (compilation) => {
let assets = compilation.assets;
let promises = Object.keys(assets).filter(item=>!item.includes('.html')).map(this.upload.bind(this));
Promise.all(promises).then((err, data) => console.log(err, data));
});
}
upload(filename) {
return new Promise((resolve, reject) => {
let {
bucket = "cnpmjs",
accessKey = defaultAccessKey,
secretKey = defaultSecretKey,
} = this.options;
let mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
let options = {
scope: bucket,
};
let putPolicy = new qiniu.rs.PutPolicy(options);
let uploadToken = putPolicy.uploadToken(mac);
let config = new qiniu.conf.Config();
let localFile = path.resolve(__dirname, "../dist", filename);
let formUploader = new qiniu.form_up.FormUploader(config);
let putExtra = new qiniu.form_up.PutExtra();
formUploader.putFile(
uploadToken,
filename,
localFile,
putExtra,
(err, body, info) => {
err ? reject(err) : resolve(body);
}
);
});
}
}
module.exports = UploadPlugin;
指纹占位符
占位符名称 | 含义 |
---|---|
ext | 资源后缀名 |
name | 文件名称 |
path | 文件的相对路径 |
folder | 文件所在的文件夹 |
hash | 每次webpack构建时生成一个唯一的hash值 |
chunkhash | 根据chunk生成hash值,来源于同一个chunk,则hash值就一样 |
contenthash | 根据内容生成hash值,文件内容相同hash值就相同 |
const path = require("path");
const glob = require("glob");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
mode: "production",
+ entry: {
+ main: './src/index.js',
+ vender:['lodash']
+ },
output:{
path:path.resolve(__dirname,'dist'),
+ filename:'[name].[hash].js'
},
devServer:{
hot:false
},
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
{
loader:'thread-loader',
options:{
workers:3
}
},
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
+ filename: "[name].[hash].css"
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
],
};
const path = require("path");
const glob = require("glob");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
mode: "production",
entry: {
main: './src/index.js',
vender:['lodash']
},
output:{
path:path.resolve(__dirname,'dist'),
+ filename:'[name].[chunkhash].js'
},
devServer:{
hot:false
},
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
{
loader:'thread-loader',
options:{
workers:3
}
},
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
+ filename: "[name].[chunkhash].css"
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
],
};
mini-css-extract-plugin
里的contenthash
值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,那么不会重复构建const path = require("path");
const glob = require("glob");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
mode: "production",
entry: {
main: './src/index.js',
vender:['lodash']
},
output:{
path:path.resolve(__dirname,'dist'),
filename:'[name].[chunkhash].js'
},
devServer:{
hot:false
},
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
{
loader:'thread-loader',
options:{
workers:3
}
},
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
+ filename: "[name].[contenthash].css"
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
],
};
cnpm i webpack-bundle-analyzer -D
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports={
plugins: [
new BundleAnalyzerPlugin() // 使用默认配置
// 默认配置的具体配置项
// new BundleAnalyzerPlugin({
// analyzerMode: 'server',
// analyzerHost: '127.0.0.1',
// analyzerPort: '8888',
// reportFilename: 'report.html',
// defaultSizes: 'parsed',
// openAnalyzer: true,
// generateStatsFile: false,
// statsFilename: 'stats.json',
// statsOptions: null,
// excludeAssets: null,
// logLevel: info
// })
]
}
{
"scripts": {
"dev": "webpack --config webpack.dev.js --progress"
}
}
webpack.config.js
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports={
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
generateStatsFile: true, // 是否生成stats.json文件
}),
]
}
{
"scripts": {
"generateAnalyzFile": "webpack --profile --json > stats.json", // 生成分析文件
"analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 启动展示打包报告的http服务器
}
}
npm run generateAnalyzFile
npm run analyz
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
});
指定extension之后可以不用在require
或是import
的时候加文件扩展名,会依次尝试添加扩展名进行匹配
resolve: {
extensions: [".js",".jsx",".json",".css"]
},
配置别名可以加快webpack查找模块的速度
bootstrap
,而不需要从node_modules
文件夹中按模块的查找规则查找const bootstrap = path.resolve(__dirname,'node_modules/_bootstrap@3.3.7@bootstrap/dist/css/bootstrap.css');
resolve: {
+ alias:{
+ "bootstrap":bootstrap
+ }
},
node_modules
目录resolve.modules
字段进行配置的
默认配置resolve: {
modules: ['node_modules'],
}
如果可以确定项目内所有的第三方依赖模块都是在项目根目录下的 node_modules 中的话resolve: {
modules: [path.resolve(__dirname, 'node_modules')],
}
默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件
resolve: {
// 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
mainFields: ['browser', 'module', 'main'],
// target 的值为其他时,mainFields 默认值为:
mainFields: ["module", "main"],
}
当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的
resolve: {
mainFiles: ['index'], // 你可以添加其他默认使用的文件名
},
resolve.resolveLoader
用于配置解析 loader 时的 resolve 配置,默认的配置:
module.exports = {
resolveLoader: {
modules: [ 'node_modules' ],
extensions: [ '.js', '.json' ],
mainFields: [ 'loader', 'main' ]
}
};
module.noParse
字段,可以用于配置哪些模块文件的内容不需要进行解析module.exports = {
// ...
module: {
noParse: /jquery|lodash/, // 正则表达式
// 或者使用函数
noParse(content) {
return /jquery|lodash/.test(content)
},
}
}...
使用 noParse 进行忽略的模块文件中不能使用 import、require、define 等导入机制
IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去
import moment from 'moment';
console.log(moment);
new webpack.IgnorePlugin(/^\.\/locale/,/moment$/)
预设 | 替代 | 描述 |
---|---|---|
errors-only | none | 只在错误时输出 |
minimal | none | 发生错误和新的编译时输出 |
none | false | 没有输出 |
normal | true | 标准输出 |
verbose | none | 全部输出 |
cnpm i friendly-errors-webpack-plugin
+ stats:'verbose',
plugins:[
+ new FriendlyErrorsWebpackPlugin()
]
编译完成后可以通过
echo $?
获取错误码,0为成功,非0为失败
.dll
为后缀的文件称为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据webpack.dll.config.js
const path = require("path");
const DllPlugin = require("webpack/lib/DllPlugin");
module.exports = {
mode: "development",
entry: {
react: ["react", "react-dom"],
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].dll.js", //react.dll.js
library: "_dll_[name]",
},
plugins: [
new DllPlugin({
name: "_dll_[name]",
path: path.join(__dirname, "dist", "[name].manifest.json"), //react.manifest.json
}),
],
};
webpack --config webpack.dll.config.js --mode=development
const path = require("path");
const glob = require("glob");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const DllReferencePlugin = require("webpack/lib/DllReferencePlugin.js");
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
mode: "development",
entry: "./src/index.js",
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
+ new DllReferencePlugin({
+ manifest: require("./dist/react.manifest.json"),
+ }),
],
};
webpack --config webpack.config.js --mode development
<script src="react.dll.js"></script>
<script src="bundle.js"></script>
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: "babel-loader",
options: {
cacheDirectory: true
}
}]
},
cnpm i cache-loader -D
const loaders = ['babel-loader'];
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
...loaders
],
include: path.resolve('src')
}
]
}
}
HardSourceWebpackPlugin
为模块提供了中间缓存,缓存默认的存放路径是 node_modules/.cache/hard-source
。`hard-source-webpack-plugin
cnpm i hard-source-webpack-plugin -D
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
entry: // ...
output: // ...
plugins: [
new HardSourceWebpackPlugin()
]
}
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
//优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下 loader 只会匹配一个
oneOf: [
...,
{},
{}
]
}
]
}
}
cnpm i thread-loader- D
const path = require("path");
const glob = require("glob");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const DllReferencePlugin = require("webpack/lib/DllReferencePlugin.js");
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
mode: "development",
entry: "./src/index.js",
module: {
rules: [
{
test: /\.js/,
include: path.resolve(__dirname, "src"),
use: [
+ {
+ loader:'thread-loader',
+ options:{
+ workers:3
+ }
+ },
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
new DllReferencePlugin({
manifest: require("./dist/react.manifest.json"),
}),
],
};
terser-webpack-plugin 开启 parallel 参数
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};