webpack 是什么?
它是一个模块打包工具
Loader 是什么?
webpack 不能识别 JavaScript 之外的文件,需要 loader 对它时行识别
file-loader
file-loader 就是在 JavaScript 代码里 import/require 一个文件时,会将该文件生成到输出目录,并且在 JavaScript 代码里返回该文件的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| module.exports = { module: { rules: [ { test: /\.(png|jpg|jpeg|gif|svg)/, use: { loader: "file-loader", options: { name: "[name].[ext]", outputPath: "images/", }, }, }, ], }, };
|
url-loader
url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| module.exports = { module: { rules: [ { test: /\.(png|jpg|gif)$/, use: [ { loader: "url-loader", options: { limit: 8192, }, }, ], }, ], }, };
|
处理 Sass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| module.exports = { module: { rules: [ { test: /\.scss$/, use: [ { loader: "style-loader", }, { loader: "css-loader", }, { loader: "sass-loader", }, { loader: "postcss-loader", options: { plugins: [require("autoprefixer")({})], }, }, ], }, ], }, };
|
处理 CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = { module: { rules: [ { test: /\.(css)$/, use: [ { loader: "style-loader", }, { loader: "css-loader" }, ], }, ], }, };
|
Plugins 是什么?
在 webpack 运行到某个时刻的时做一些事情
HtmlWebpackPlugin
会在打包结束后,自动生成一个 html 文件,并把打包生成的 JS 文件自动引入到这个 html 文件里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var HtmlWebpackPlugin = require("html-webpack-plugin"); var path = require("path");
module.exports = { entry: "index.js", output: { path: path.resolve(__dirname, "./dist"), filename: "index_bundle.js", }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", }), ], };
|
CleanWebpackPlugin
用于在下一次打包时清除之前打包的文件
1 2 3 4 5 6 7 8 9 10 11
| const { CleanWebpackPlugin } = require("clean-webpack-plugin"); var path = require("path");
module.exports = { entry: "index.js", output: { path: path.resolve(__dirname, "./dist"), filename: "index_bundle.js", }, plugins: [new CleanWebpackPlugin()], };
|
SourceMap
可以将编译后的代码映射回原始源代码,用来追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。
1 2 3 4 5 6 7 8
| module.exports = { devtool: "source-map", };
|
最佳实践
1 2
| devtool:"cheap-module-eval-source-map", devtool:"cheap-module-source-map",
|
devServer
1 2 3 4 5 6 7 8 9 10
| let path = require("path");
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), open: true, port: 9000, }, };
|
Hot Module Replacement
它允许在项目在修改的时候,无需完全刷新页面。
1 2 3 4 5 6 7 8 9
| const webpack = require("webpack");
module.exports = { devServer: { hot: true, hotOnly: true, }, plugins: [new webpack.HotModuleReplacementPlugin()], };
|
使用 Bable 编译 ES6
bable 转 ES6 相关:
babel-loader
: 负责 es6 语法转化
babel-preset-env
: 包含 es6、7 等版本的语法转化规则
babel-polyfill
: es6 内置方法和函数转化垫片
babel-plugin-transform-runtime
: 避免 polyfill 污染全局变量
需要注意的是, babel-loader
和babel-polyfill
。前者负责语法转化,比如:箭头函数;后者负责内置方法和函数,比如:new Set()
。
1 2
| npm install --save-dev babel-loader @babel/core npm install @babel/preset-env --save-dev
|
1 2 3 4 5 6 7 8 9 10 11 12
| module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, ]; }
|
如果需要对Promise、map
之类的方法做兼容处理则需要安装babel-polyfill
1
| npm install --save @babel/polyfill
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: { presets: [ ["@babel/preset-env"], { targets: { edge: "17", firefox: "60", chrome: "67", safari: "11.1", }, useBuiltIns: "usage", }, ], }, }, ]; }
|
注意:使用 "useBuiltIns": "usage"
后,不需要在项目里在引入import '@babel/polyfill'
默认polyfill
会污染全局变量,如果开发一个第三方模块或者库的时候则需要用@babel/runtime
1 2
| npm install --save @babel/runtime npm install --save @babel/runtime-corejs2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: { plugins: [ [ "@babel/plugin-transform-runtime", { absoluteRuntime: false, corejs: 2, helpers: true, regenerator: true, useESModules: false, }, ], ], }, }, ]; }
|
如果配置文件较多时,可以项目内新建一个.babelrc
,然后把options
里的内容放进去
.babelrc
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "plugins" : [ [ "@babel/plugin-transform-runtime", { "absoluteRuntime": false, "corejs": 2, "helpers": true, "regenerator": true, "useESModules": false } ] ] }
|
wepback.config.js
1 2 3 4 5 6 7 8 9
| module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", }, ]; }
|
Tree Shaking
清除到代码中无用的js
代码,只支持ES Module
,也就是只支持import
(静态引入)方式引入,不支持CommonJS
(动态引入)的方式引入
在mode
是production
时就会进行Tree Shaking
,为了方便你的调试可以在配置文件中加入如下代码:
1 2 3
| optimization: { usedExports: true; }
|
package.json
1 2 3 4 5
|
sideEffects: false sideEffects: ["@babel/polyfill"] sideEffects: ["*.css"]
|
Code Splitting
异步的代码 (import
)webpack
会自动的进行代码分割,同步代码则需要进行如下配置:
1 2 3 4 5
| optimization: { splitChunks: { chunks: "all"; } }
|
SplitChunksPlugin 配置详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| module.exports = { optimization: { splitChunks: { chunks: "async", minSize: 30000, minRemainingSize: 0, maxSize: 0, minChunks: 1, maxAsyncRequests: 6, maxInitialRequests: 4, automaticNameDelimiter: "~", automaticNameMaxLength: 30, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10, }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true, }, }, }, }, };
|
如果对代码进行代码分割,它会去看cacheGroups.vendors
这个参数,如果是false
则不会进行代码分割,反之,如果引入的库是cacheGroups.vendors.test
里的,则会代码分割,如果不是cacheGroups.vendors.test
里的,会去看cacheGroups.default
这个参数,如果是false
则不会进行代码分割,反之则会
为什么 splitChunks.chunks
的默认值是async
因为异步的代码才能提高打包的性能,而同步代码则只能增加缓存
CSS Code Splitting
1
| npm install --save-dev mini-css-extract-plugin
|
webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", chunkFilename: "[id].css", }), ], module: { rules: [ { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: "../", }, }, "css-loader", ], }, ], }, };
|
prefetch/preload module
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:
- prefetch(预取):当核心代业务代码加载完成后,空闲时候后在加载需要的资源
- preload(预加载):和核心代业务代码一起进行并行加载
下面这个 prefetch 的简单示例中,有一个 HomePage
组件,其内部渲染一个 LoginButton
组件,然后在点击后按需加载 LoginModal
组件。
LoginButton.js
这会生成 <link rel="prefetch" href="login-modal-chunk.js">
并追加到页面头部,指示着浏览器在闲置时间预取 login-modal-chunk.js
文件。