本文共 30703 字,大约阅读时间需要 102 分钟。
本文由浅入深,讲述 webpack 如何从零开始配置项目打包所需。
建议配合视频食用 提取码:pc4r
欢迎各位一键三连,就当给我家淘淘攒狗粮了(#.#)
npm install webpack webpack-cli -g
npm uninstall webpack webpack-cli -g
npm install webpack webpack-cli -D
npm install webpack@4.25.0 webpack-cli -D
npm info webpack
webpack -v
npx webpack -v
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。(大白话来说,就是模块打包工具,将多个模块打包到生成一个最终的bundle.js问题)
项目中想要使用 webpack 首先要 npm init -y
初始化,安装 webpack npm install webpack webpack-cli
根目录下新建一个 webpack.config.js
webpack 假定项目的入口起点为 src/index,会在 dist/bundle.js 输出结果,并且在生产环境开启压缩和优化。
const path = require('path')module.exports = { // 入口文件 entry: './src/index.js', // 出口文件 output: { // 打包之后的文件名 filename: 'bundle.js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist') }}
这是基本配置。
运行npx webpack index.js
, 如果希望使用 npm run ****
来运行项目,则需要在 package.json
里面配置
{ "name": "day01", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { // 配置这个 "bundle": "webpack" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "webpack": "^4.42.1", "webpack-cli": "^3.3.11" }}
打包出来的,终端输出的信息
Hash: 1097c683fef4192a87d3 // 哈希值Version: webpack 4.42.1 // webpack 版本号Time: 177ms // 花费时间Built at: 2020-04-15 15:35:05 // 运行时间 Asset Size Chunks Chunk Namesbundle.js 1.36 KiB 0 [emitted] main //打包后的文件名,大小,id,入口文件名Entrypoint main = bundle.js[0] ./src/index.js 149 bytes { 0} [built][1] ./src/header.js 208 bytes { 0} [built][2] ./src/silder.js 208 bytes { 0} [built][3] ./src/content.js 214 bytes { 0} [built]WARNING in configurationThe 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
底下警告的解决方式,在 webpack.config.js
里面,开头添加一句
module.exports = { mode: "production", // 入口文件 entry: './src/index.js', // 出口文件 output: { // 打包之后的文件名 filename: 'bundle.js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist') }}
也可以添加一句 mode: "development"
,两者的不同在于:production
会对打包后的文件压缩。
production
development
webpack可以使用 loader 来预处理文件,通过使用不同的Loader,webpack可以把不同的静态文件进行编译。loader就是一个打包的方案,它知道对于某个特定的文件该如何去打包。 本身webpack不清楚对于一些文件如何处理,loader知道怎么处理,所以webpack就会去求助于loader。
常见的loader
- raw-loader:加载文件原始内容(utf-8)
- file-loader:将文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
- url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值时返回其 publicPath,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
- source-map-loader:加载额外的 Source Map 文件,以方便断点调试
- svg-inline-loader:将压缩后的 SVG 内容注入代码中
- image-loader:加载并且压缩图片文件
- json-loader 加载 JSON 文件(默认包含)
- handlebars-loader: 将 Handlebars 模版编译成函数并返回
- babel-loader:把 ES6 转换成 ES5
- ts-loader: 将 TypeScript 转换成 JavaScript
- awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
- style-loader:将 CSS 代码注入 JavaScript 中,通过 DOM 操作去加载 CSS
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
- postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
- eslint-loader:通过 ESLint 检查 JavaScript 代码
- tslint-loader:通过 TSLint检查 TypeScript 代码
- mocha-loader:加载 Mocha 测试用例的代码
- coverjs-loader:计算测试的覆盖率
- vue-loader:加载 Vue.js 单文件组件
- i18n-loader: 国际化
- cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里
npm install file-loader -D
entry: './src/index.js', module: { rules: [{ test: /\.(jpg|png|gif)$/, use: { loader: 'file-loader', options: { // placeholder 占位符 // 打包后的图片名字,后缀和打包的之前的图片一样 name: '[name]_[hash].[ext]', // 打包输出之后的文件夹 outputPath: 'images/' } } }]}
npm install url-loader --save-dev
// 其他不变loader: 'url-loader',
运行 npm run bundle
之后,发现 dist
目录下并没有打包之后的图片,这时的图片是被打包在 bundle.js
里面,以 base64
格式存在。
这样做的好处是减少请求的次数,缺点是如果图片太大,那么请求的时间也会过长。所以可以在添加 limit: 2048
配置项。小于限制大小时,以base64格式存在 js 文件中 ,大于这个值时,以图片的形式存在
npm install style-loader css-loader --save-dev
{ test: /\.css$/, use: ['style-loader', 'css-loader'],}
css-loader:分析页面css之间的关系,将所有的css文件都集合到一起,
style-loader:把 css-loader 处理好之后的文件,挂载到 header 之间npm install sass-loader sass --save-dev
{ test: /\.scss$/, use: [ 'style-loader', 'style-loader', 'sass-loader' ],}
loader 的执行顺序是 从下到上 从右到左
先加载sass-loader翻译成css文件,然后使用css-loader打包成一个css文件,通过style-loader挂载到页面上去。npm i -D postcss-loader
npm install autoprefixer -D
根目录下新建 postcss.config.js
module.exports = { plugins: [ require('autoprefixer') ]}
{ test: /\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader', 'postcss-loader' ],}
使用 postcss-loader 的时候,会去寻找 postcss.config.js
文件,并且引用 autoprefixer
这个插件。
当在项目的 scss 文件当中也引用了 sass 文件,这时就需要添加 importLoaders: 2
。这样无论你是在js中引入scss文件,还是在scss中引入scss文件,都会重新依次从下往上执行所以loader。
use: [ 'style-loader', { loader: 'css-loader', options: { // /sass文件里引入另外一个sass文件,另一个文件还会从postcss-loader向上解析 // 如果不加,就直接从css-loader开始解析。 importLoaders: 2, // 开启css的模块打包, css样式不会和其他模块发生耦合和冲突 modules: true } }, 'sass-loader', 'postcss-loader']
npm install file-loader --save-dev
{ test: /\.(eot|ttf|svg)$/, use: { loader: 'file-loader' }}
可以在webpack运行到某个时刻的时候,帮你做一些事情
常见的 plugin
- define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
- ignore-plugin:忽略部分文件
- html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
- web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
- uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
- terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
- webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
- mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
- serviceworker-webpack-plugin:为网页应用增加离线缓存功能
- clean-webpack-plugin: 目录清理
- ModuleConcatenationPlugin: 开启 Scope Hoisting
- speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
- webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
npm install --save-dev html-webpack-plugin
htmlWebpackPlugin 会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { mode: "development", entry: './src/index.js', // ....... plugins: [new HtmlWebpackPlugin({ // 打包的时候,以 index.html 为模板。 template: 'src/index.html' })], output: { // 打包之后的文件名 filename: 'bundle.js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist') }}
npm i clean-webpack-plugin -D
多次打包的时候,我们希望dist目录可以在每次打包的时候,自动删除,不会影响到下一次的打包// ....const { CleanWebpackPlugin } = require('clean-webpack-plugin');module.exports = { mode: "development", entry: './src/index.js', // ....... plugins: [ // 以 index.html 为模板。 // 打包后执行 new HtmlWebpackPlugin({ template: 'src/index.html' }), // 打包前执行 new CleanWebpackPlugin() ], // 出口文件 output: { // 打包之后的文件名 filename: 'index.js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist') }}
entry: { main: './src/index.js', sub: './src/index.js'},output: { // 打包之后的文件名 // 占位符:生成两个文件,不会报错,对于的名字就是entry名称对应 filename: '[name].js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist')}
output: { // ... // 如果后台已经将资源挂载到了cdn上,那么你的publicPath就会把路径前做修改加上publicPath值 publicPath: 'http://cdn.com.cn', // ...}
线上环境有三种处理方案:
hidden-source-map:借助第三方错误监控平台 Sentry 使用
nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)
注意:避免在生产中使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。
module.exports = { mode: "development", // 开启 source-map devtool: 'source-map', // 入口文件 entry: { main: './src/index.js', }, // ..... output: { // 打包之后的文件名 filename: '[name].js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist') }}
开启 source-map --------- devtool: source-map
map 文件会以 base64 格式存在于 main.js 文件中,映射到某一行的某一个字符(耗费性能) ----- -devtool: inline-source-map
映射到某一行(不会映射到第三方模块错误)-------- devtool: cheap-inline-source-map
映射到某一行(映射到第三方模块错误)------- devtool: cheap-module-inline-source-map
速度最快,性能最佳 --------- devtool: eval
开发环境------------------- devtool: cheap-module-eval-source-map
线上环境 ------------------- devtool: cheap-module-source-map
package.json
"scripts": { // 运行 npm run watch 是 会监视要打包的文件,一旦发生变化,就会重新打包 "watch": "webpack --watch", // 使用 webpack-dev-server 启动一个服务器 "start": "webpack-dev-server"},
module.exports = { mode: "development", devtool: 'cheap-module-eval-source-map', devServer: { // 配置开发服务运行时的文件根目录 contentBase: './dist', // 自动打开浏览器 // host:开发服务器监听的主机地址 // compress :开发服务器是否启动gzip等压缩 // port:开发服务器监听的端口 open: true, // 接口代理 proxy: { '/api': 'http://localhost:3000' } }, // ....... // 入口文件 entry: { main: './src/index.js', }, output: { // 打包之后的文件名 filename: '[name].js', // 打包之后文件的存放路径 path: path.resolve(__dirname, 'dist') }}
devServer可以实时检测文件是否发生变化
使用 webpack-dev-server 打包的话,不会生成 dist 目录,而是将你的文件打包到内存中
const webpack = require('webpack')module.exports = { mode: "development", devtool: 'cheap-module-eval-source-map', devServer: { contentBase: './dist', open: true, // 开启 HMR hot: true, // 即使 HMR 不生效,浏览器也不会刷新 hotOnly: true }, // ...... plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin(), // HMR new webpack.HotModuleReplacementPlugin() ]}
注意的内容是,对于css的内容修改,css-loader底层会帮我们做好实时热更新,对于JS模块的话,我们需要手动的去配置。
Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin
来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。
npm install --save-dev babel-loader @babel/core
是babel中的一个核心库
npm install @babel/preset-env --save-dev
babel-loader并没有把es6 的语法转成 es5,只是两者之间搭建了一个桥梁而已,@babel/preset-env包含 es6转化成 es5的语法
npm install --save @babel/polyfill
补齐es6转 es5 缺少的函数。将Promise,map等低版本中没有实现的语法,用polyfill来实现。
{ test: /\.js$/, // exclude参数: node_modules目录下的js文件不需要做转es5语法,也就是排除一些目录 exclude: /node_modules/, loader: 'babel-loader', options: { // 大于 67版本的,不需要转化 presets: [['@babel/preset-env', { "targets": { "chrome": "67", }, // 有了preset-env这个模块后, const语法被翻译成成var。 // 但对于Promise以及map这些语法,低版本浏览器是不支持的。 // 需要@babel/polyfill模块,对Promise,map进行补充,完成该功能。 // 但用完这个以后,打包的文件体积瞬间增加了10多倍之多。 // 因为@babel/polyfill为了弥补Promise,map等语法的功能, // 该模块就需要自己去实现Promise,map等语法的功能,这也就是为什么打包后的文件很大的原因。 // 添加业务代码缺失的转化之后的函数,不会把所有的都添加进去 useBuiltIns: 'usage' }]] }},
开发库文件时,上面的场景使用babel会污染环境。这个时候,我们需要换一种方案来解决。
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
(core 版本为 2 时,需要的配置)
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { // 添加业务代码缺失的转化之后的函数 // 大于 67版本的,不需要转化 // presets: [['@babel/preset-env', { // "targets": { // "chrome": "67", // }, // useBuiltIns: 'usage' // }]] "plugins": [[ "@babel/plugin-transform-runtime", { "absoluteRuntime": false, "corejs": 2, "helpers": true, "regenerator": true, "useESModules": false, "version": "7.0.0-beta.0" } ]] }},
大多数JavaScript Parser遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器)
Babel大概分为三大部分:
如果程序是一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
通俗意义而言,当你引入一个模块时,你可能用到的只是其中的某些功能,这个时候,我们不希望这些无用
的代码打包到项目中去。通过tree-shaking,就能将没有使用的模块摇掉,这样达到了删除无用代码的目的。
webpack4默认的production下是会进行tree-shaking的
mode: "development",// ......plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' new CleanWebpackPlugin(), new webpack.HotModuleReplacementPlugin()],//在开发环境中加,生产环境不加optimization: { usedExports: true},// 出口文件output: { filename: '[name].js', path: path.resolve(__dirname, 'dist')}
如果我们的模块不是达到很纯粹,这个时候,webpack就无法识别出哪些代码需要删除,所以,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”。
这种方式是通过 package.json 的 "sideEffects"
属性来实现的。
{ "name": "webpack-demo", "sideEffects": false}
如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false
,来告知 webpack,它可以安全地删除未用到的 export 导出。
注意,任何导入的文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似
css-loader
并导入 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:
{ "name": "webpack-demo", "sideEffects": [ "*.css" ]}
npm install webpack-merge -D
开发环境和生成环境中,所侧重的功能是不一样的
开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。
生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。
把 webpack.config.js 文件修改为 webpack.dev.js,新建 webpack.prod.js 复制一份 webpack.dev.js 中的内容
,新建一个 webpack.common.js 文件(作为公共模块)webpack.common.js
const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin');const { CleanWebpackPlugin } = require('clean-webpack-plugin');module.exports = { entry: { main: './src/index.js', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.(jpg|png|gif)$/, use: { loader: 'url-loader', options: { name: '[name]_[hash].[ext]', outputPath: 'images/', limit: 2048 } } }, { test: /\.scss$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'sass-loader', 'postcss-loader' ], }, { test: /\.css$/, use: [ 'style-loader', 'css-loader', 'postcss-loader' ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin() ], output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') }}
webpack.dev.js
const webpack = require('webpack')const merge = require('webpack-merge')const commonConfig = require('./webpack.common.js')const devConfig = { mode: "development", devtool: 'cheap-module-eval-source-map', devServer: { contentBase: './dist', open: true, hot: true }, plugins: [ new webpack.HotModuleReplacementPlugin() ], optimization: { usedExports: true },}// 合并两个模块module.exports = merge(commonConfig, devConfig)
webpack.prod.js
const merge = require('webpack-merge')const commonConfig = require('./webpack.common.js')const prodConfig = { mode: "production", devtool: 'cheap-module-source-map'}// 合并两个模块module.exports = merge(commonConfig, prodConfig)
一般的脚手架中,会有一个 build 文件夹,我们就可以把刚刚的三个文件移入其中。
package.json
"scripts": { "dev-build": "webpack --config ./build/webpack.dev.js", "dev": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js"},
代码分割的本质其实就是在源代码直接上线和打包成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。有什么意义呢? 用可接受的服务器性能压力增加来换取更好的用户体验
代码分割,和webpack无关,为了提升性能,webpack有插件,可以很好的实现代码分割
plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin()],// 代码分割optimization: { splitChunks: { chunks: 'all' }},// 代码分割output: { filename: '[name].js', path: path.resolve(__dirname, '../dist')}
当有多个入口文件,或者是打包文件需要做一个划分,举个例子,比如第三方库lodash,jquery等库需要打包到一个目录下,自己的业务逻辑代码需要打包到一个文件下,这个时候,就需要提取公共模块了。
npm install --save-dev @babel/plugin-syntax-dynamic-import
.babelrc
{ "presets": [ [ "@babel/preset-env", { "targets": { "chrome": "67" }, "useBuiltIns": "usage" } ], "@babel/preset-react" ], "plugins": ["@babel/plugin-syntax-dynamic-import"]}
webpack.common.js
optimization: { splitChunks: { //启动代码分割,不写有默认配置项 chunks: 'all', //参数all/initial/async,只对所有/同步/异步进行代码分割 minSize: 30000, //大于30kb才会对代码分割 maxSize: 0, minChunks: 1,//打包生成的文件,当一个模块至少用多少次时才会进行代码分割 maxAsyncRequests: 5,//同时加载的模块数最多是5个 maxInitialRequests: 3,//入口文件最多3个模块会做代码分割,否则不会 automaticNameDelimiter: '~',//文件自动生成的连接符 name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10, //谁优先级大就把打包后的文件放到哪个组 filename: 'vendors.js' }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true,//模块已经被打包过了,就不用再打包了,复用之前的就可以 filename: 'common.js' //打包之后的文件名 } } }},
用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载 在给单页应用做按需加载优化时,一般采用以下原则:
plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin()],optimization: { splitChunks: { chunks: 'all', }},output: { filename: '[name].js', path: path.resolve(__dirname, '../dist')}
output: { filename: '[name].js', // main.js异步加载的间接的js文件。用来打包import('module')方法中引入的模块 // 第三方模块 chunkFilename: '[name].chunk.js', path: path.resolve(__dirname, '../dist')}
把CSS单独提取出来加载, 提高性能
npm install --save-dev mini-css-extract-plugin
:一般在线上环境使用这个插件,因为在开发环境中不支持HMR。 npm i optimize-css-assets-webpack-plugin -D
压缩css文件
webpack
配置文件 webpack.common.js
module.exports = { // ..... output: { filename: '[name].js', // main.js异步加载的间接的js文件。用来打包import('module')方法中引入的模块 // 第三方模块 chunkFilename: '[name].chunk.js', path: path.resolve(__dirname, '../dist') }}
webpack.prod.js
// 引入两个插件const MiniCssExtractPlugin = require('mini-css-extract-plugin')const merge = require('webpack-merge')const commonConfig = require('./webpack.common.js')const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');const prodConfig = { mode: "production", devtool: 'cheap-module-source-map', module: { rules: [ { test: /\.scss$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 2 } }, 'sass-loader', 'postcss-loader' ] }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader' ] } ] }, optimization: { minimizer: [new OptimizeCSSAssetsPlugin({ })], }, plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', //直接引用的css文件 chunkFilename: '[name].chunk.css' //间接引用的css文件 }) ]}module.exports = merge(commonConfig, prodConfig)
package.json
"sideEffects": [ "*.css" //除了css文件,其余的都TreeShaking ],
当你项目即将上线时,有一个需求,你只是修改了部分的文件,只希望用户对于其他的文件,依旧去采用浏览器缓存中的文件,所以这个时候,我们需要用到contenthash
。
webpack中关于hash,有三种,分别是
**hash:**主要用于开发环境中,在构建的过程中,当你的项目有一个文件发现了改变,整个项目的hash值就会做修改(整个项目的hash值是一样的),这样子,每次更新,文件都不会让浏览器缓存文件,保证了文件的更新率,提高开发效率。
**chunkhash:**跟打包的chunk有关,具体来说webpack
是根据入口entry
配置文件来分析其依赖项并由此来构建该entry的chunk
,并生成对应的hash
值。不同的chunk
会有不同的hash
值。
在生产环境中,我们会把第三方或者公用类库进行单独打包,所以不改动公共库的代码,该chunk
的hash
就不会变,可以合理的使用浏览器缓存了。
但是这个中hash的方法其实是存在问题的,生产环境中我们会用webpack
的插件,将css
代码打单独提取出来打包。这时候chunkhash
的方式就不够灵活,因为只要同一个chunk
里面的js修改后,css
的chunk
的hash
也会跟随着改动。因此我们需要contenthash
。
contenthash:contenthash
表示由文件内容产生的hash
值,内容不同产生的contenthash
值也不一样。生产环境中,通常做法是把项目中css
都抽离出对应的css
文件来加以引用。
对于webpack,旧版本而言,即便每次你npm run build,内容不做修改的话,contenthash值还是会有所改变,这个是因为,当你在模块之间存在相互之间的引用关系,有一个manifest文件。
manifest文件是用来引导所以模块的交互,manifest文件包含了加载和处理模块的逻辑,举个例子,你的第三方库打包后的文件,我们称之为vendors,你的逻辑代码称为main,当你webpack生成一个bundle时,它同时会去维护一个manifest文件,你可以理解成每个bundle文件存在这里信息,所以每个bundle之间的manifest信息有不同,这样子我们就需要将manifest文件给提取出来。
这个时候,需要在optimization中增加一个配置
webpack.common.js
optimization: { //兼容老版本webpack4,把manifest打包到runtime里,不影响业务代码和第三方模块 runtimeChunk: { name: 'manifest' }, usedExports: true, splitChunks: { // ... }},performance: false, //禁止提示性能上的一些问题output: { path: path.resolve(__dirname, '../dist')}
webpack.dev.js
plugins: [ new webpack.HotModuleReplacementPlugin()],output: { filename: '[name].js', chunkFilename: '[name].js',}
webpack.prod.js
optimization: { minimizer: [new OptimizeCSSAssetsPlugin({ })],},plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].chunk.css' })],output: { filename: '[name].[contenthash].js', // 源代码不变,hash值就不会变,解决浏览器缓存问题。 // 打包上线时,用户只需要更新有变化的代码,没有变化的从浏览器缓存读取 chunkFilename: '[name].[contenthash].js'}
当你再使用第三方库,此时需要引入它,又或者是你有很多的第三方库或者是自己写的库,每个js文件都需要依赖它,让人很繁琐,这个时候,shimming就派上用场了。
const webpack = require('webpack')// ....plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin(), // 发现模块使用 $ ,会自动引用 jquery new webpack.ProvidePlugin({ $: 'jquery', _join: ['lodash', 'join']//_join代表lodash里的join方法 })],
项目中的文件
export const Arr_add = arr=>{ let str = _.join(arr,'++'); return str;}
只需要一个common.js文件通过在package.json中传递不同的参数,区分是开发环境还是生产环境。
const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin');const { CleanWebpackPlugin } = require('clean-webpack-plugin');const webpack = require('webpack')const merge = require('webpack-merge')const devConfig = require('./webpack.dev')const prodConfig = require('./webpack.prod')const commonConfig = { entry: { main: './src/index.js', }, module: { // .... }, plugins: [ // ..... ], optimization: { // .... }, performance: false, //禁止提示性能上的一些问题 output: { path: path.resolve(__dirname, '../dist') }}module.exports = (env) => { if (env && env.production) { return merge(commonConfig, prodConfig) } else { return merge(commonConfig, devConfig) }}
通过–env.production,把环境变量传进去
"scripts": { "dev-build": "webpack --config ./build/webpack.common.js", "dev": "webpack-dev-server --config ./build/webpack.common.js", "build": "webpack --env.production --config ./build/webpack.common.js"},
webpack.config.js
具体代码
const path = require('path');module.exports = { mode: 'production', entry: './src/index.js', // 忽略 lodash ,不打包 // externals: ['lodash'], externals: { // 当用户引用 lodash 这个库文件的时候,必须使用 lodash 这个名字 lodash: { commonjs: 'lodash' } }, output: { path: path.resolve(__dirname, 'dist'), filename: 'library.js', library: 'library', // 支持 script 标签引入 libraryTarget: 'umd' // import const require 无论什么方法引入,都支持 // libraryTarget: 'this' // library 挂载到 this }}
package.json
"main": "./dist/library.js", "scripts": { "build": "webpack"},
在离线(offline)时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的 web 技术来实现的。线上环境时才用到pwa,开发时不需要
为了模拟环境,安装npm i http-server -D
npm install workbox-webpack-plugin --save-dev
package.json
"scripts": { "start": "http-server dist", "dev": "webpack-dev-server --config ./build/webpack.common.js", "build": "webpack --env.production --config ./build/webpack.common.js"},
webpack.prod.js
const WorkboxPlugin = require('workbox-webpack-plugin');// ....plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].chunk.css' }), new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true })],
npm install --save-dev typescript ts-loader
npm install --save-dev @types/lodash
webpack.config.js
const path = require('path')module.exports = { mode: 'production', entry: './src/index.tsx', module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ } ] }, output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }}
tsconfig.json
{ "compilerOptions": { "outDir": "./dist", "module": "es6", "target": "es5", "allowJs": true }}
npm i axios -D
以 react 为例
class App extends Component { componentDidMount() { axios.get('/react/api/header.json').then((res) => { console.log(res) }) } render() { returnhello react}}
webpack.config.js
devServer: { contentBase: './dist', open: true, hot: true, hotOnly: true, proxy: { '/react/api': { target: 'http://www.naiyoutaozi.com', //对https协议的网址的请求的转发 secure: false, // bypass: function (req, res, proxyOptions) { // // 请求是 html 的情况,直接返回这个 html ,不会走代理 // if (req.headers.accept.indexOf('html') !== -1) { // console.log('Skipping proxy for browser request.'); // return '/index.html'; // } // }, pathRewrite: { 'header.json': 'demo.json' }, //解决网站对接口的限制 changeOrigin: true, //变更请求头 headers: { host: 'www.dell-lee.com', cookie: 'asvafhgs' } } }},
npm i react-router-dom --save
proxy: { '/react/api': { target: 'http://www.dell-lee.com', secure: false, // historyApiFallback: { // rewrites: [//访问任何路径都展示index.html页面 // { from: /\.*/, to: '/index.html' }, // ] // }, // 一般配置这个就可以了 historyApiFallback: true, // bypass: function (req, res, proxyOptions) { // // 请求是 html 的情况,直接返回这个 html ,不会走代理 // if (req.headers.accept.indexOf('html') !== -1) { // console.log('Skipping proxy for browser request.'); // return '/index.html'; // } // }, pathRewrite: { 'header.json': 'demo.json' }, changeOrigin: true, headers: { host: 'www.dell-lee.com', cookie: 'asvafhgs' } }}
npm install eslint -D
npm install babel-eslint -D
npm install eslint-loader -D
//快速生成eslint配置npx eslint --init
.eslintrc.js
module.exports = { "env": { "browser": true, "es6": true }, "extends": [ "plugin:react/recommended", "airbnb" ], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parser": "babel-eslint", "parserOptions": { "ecmaFeatures": { "jsx": true }, "ecmaVersion": 2018, "sourceType": "module" }, "plugins": [ "react" ], "rules": { }};
webpack.config.js
devServer: { // eslint 报错显示在浏览器 overlay: true, contentBase: './dist',},{ test: /\.js$/, exclude: /node_modules/, // use: [ // 'babel-loader', // { // loader: 'eslint-loader', // options: { // fix: true // } // } // ] // use: [ // { // loader: 'eslint-loader', // options: { // fix: true // }, // // 强制执行 // force: 'pre' // }, // 'babel-loader' // ] loader: 'babel-loader'},
使用高版本的 Webpack 和 Node.js
多进程/多实例构建:thread-loader, parallel-webpack,happypack
压缩代码
图片压缩
缩小打包作用域
提取页面公共资源
DLL:
充分利用缓存提升二次构建速度:
Tree shaking
Scope hoisting
动态Polyfill
webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在特定的阶段钩入想要添加的自定义功能。Webpack 的 Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。
转载地址:http://txfmz.baihongyu.com/