HMR:
/* HMR:hot module replacement 热模块替换,模块热替换 作用:一个模块发生变化,只会重新打包这一个模块,而不是重新打包所有模块,极大地提升了构建速度 样式文件:可以使用HMR功能,style-loader内部实现了 js文件:默认不能使用HMR功能,需要修改js代码,添加支持HMR功能的代码 注意:HMR功能对js的处理,只能处理非入口文件的其他文件 html文件:默认不能使用HMR功能,同时会导致问题:html文件不能热更新了(只有一个html文件,不用做HMR功能) 解决:修改entry入口,将html文件引入 */ const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // entry: './src/js/index.js', entry: ['./src/js/index.js', './src/index.html'], output: { filename: 'js/built.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] // 开发环境使用style-loader,内置了HMR可以使打包速度更快,生产环境提取成单个文件 }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(jpg|png|jif)$/, loader: 'url-loader', options: { limit: 6 * 1024, name: '[hash:10].[ext]', exModule: false, outputPath: 'images' } }, { test: /\.html$/, loader: 'html-loader' }, { exclude: /\.(css|js|html|jpg|png|jif|less)$/, loader: 'file-loader', options: { name: '[hash:10].[ext]', outputPath: 'media' } } ] }, plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })], mode: 'development', devServer: { contentBase: resolve(__dirname, 'build'), compress: true, open: true, port: 3000, hot: true // 开启HMR功能 } }
注意:
1、devServer中设置 hot:true 即开启了HMR功能,修改webpack.config.js后要重启webpack服务:npx webpack-dev-server
2、css文件使用style-loader,它的内部是默认开启了HMR功能的,开发环境使用style-loader,生产环境将css提取为单独文件
3、html文件只有一个,它不用做HMR,但是在开启HMR后没有热更新了,解决:在entry中将html文件引入
4、js文件需要添加支持HMR功能的代码:
// accept() 第一个参数指示哪个文件需要HMR,第二个参数是一个回调函数 if (module.hot) { module.hot.accept('./print.js', function () { print() }) module.hot.accept('./test.js', function () {}) }
5、js文件的HMR功能针对除了入口文件以外的其他js文件,入口文件一旦修改,页面会整体刷新
source-map:
devtool: 'eval-source-map'
source-map:一种提供源代码到构建后代码映射的技术,如果构建后代码出错了,用过映射可以追踪源代码错误
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
source-map:外部 错误代码准确信息 源代码的错误位置
inline-source-map:内联 只生成一个内联source-map 错误代码准确信息 源代码的错误位置 √只能开发用
hidden-source-map:外部 错误代码错误原因,但是没有错误位置 不能追踪源代码错误,只能提示到构建后代码的错误位置 *隐藏源码
eval-source-map:内联 每一个文件都生成对应的source-map,都在eval()函数中 错误代码准确信息 源代码的错误位置 √只能开发用
nosources-source-map:外部 错误代码准确信息,但是没有任何源代码信息 *隐藏全部源码
cheap-source-map:外部 错误代码准确信息 源代码的错误位置 只能精确到行
cheap-module-source-map:外部 错误代码准确信息 源代码的错误位置 module会将loader的source map加入
内联和外部的区别:
1、外部生成了文件,内联没有
2、内联构建速度更快
开发环境:速度快,调试更友好
速度快 eval>inline>cheap>...
eval-cheap-soucre-map cheap会把列去掉,只精确到行
eval-source-map
调试更友好
source-map
cheap-module-source-map
cheap-source-map
结论:eval-source-map 速度快,脚手架用的 eval-cheap-module-source-map 加上cheap调试变差,性能会变强
生产环境:源代码要不要隐藏?调试要不要更友好
内联会让代码体积变大,所以在生产环境不用内联
nosources-source-map 全部隐藏
hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
结论:source-map 调试最友好 cheap-module-source-map 加了cheap速度快
oneOf:
oneOf表示以下loader只会匹配一个,优化生成环境打包构建速度 注意:不能有两个loader处理一种类型文件,将eslint-loader放到外面
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 提取css成单独文件 const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') // 压缩css process.env.NODE_ENV = 'production' // browserslist默认使用生产环境配置,这里可以手动修改为开发环境配置 // 复用loader const commonCssLoader = [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', // 需要在package.json中定义browserslist options: { ident: 'postcss', plugins: () => [require('postcss-preset-env')()] } } ] module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, enforce: 'pre', // 一般情况下,一个文件只能被一个loader处理,当一个文件需要被多个loader处理,需要指定loader的先后顺序:先执行eslint再执行babel 原因:eslint语法检查错误后面的事再去做就没有意义;babel是将es6转为es5,如果先执行eslint,转化后的var会被eslint报错 loader: 'eslint-loader', // 在package.json中定义eslintConfig options: { fix: true } // 自动修复eslint的错误 }, { // oneOf表示以下loader只会匹配一个,优化生成环境打包构建速度 注意:不能有两个loader处理一种类型文件,将eslint-loader放到外面 oneOf: [ { test: /\.css$/, use: [...commonCssLoader] }, { test: /\.less$/, use: [...commonCssLoader, 'less-loader'] // 执行过程:less-loader将less文件编译成css文件,postcss-loader对css做兼容性处理,css-loader将css加载到js中,MiniCssExtractPlugin.loader提取css为单独文件 }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', // 按需加载 corejs: { version: 3 }, targets: { chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17' } } ] ] } }, { test: /\.(jpg|png|gif)$/, loader: 'url-loader', options: { limit: 6 * 1024, esModule: false, outputPath: 'images', // 打包完后css引入的图片路径会有问题 // publicPath: '../images', // 加了这句,css的图片引入没有问题,但是html中的图片引入有问题 name: '[hash:10].[ext]' } }, { test: /\.html$/, loader: 'html-loader' // 处理html中的img,需要关闭ES6的模块化 esModule: false }, { exclude: /\.(js|css|less|html|jpg|png|jif)$/, loader: 'file-loader', options: { outputPath: 'media' } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }), new MiniCssExtractPlugin({ filename: 'css/built.css' }), // 提取css成单独文件 new OptimizeCssAssetsWebpackPlugin() // 压缩css ], mode: 'production', // 生成模式下js自动压缩devServer: devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true } }
缓存:
babel缓存: cacheDirectory: true 作用:让第二次打包构建速度更快
文件资源缓存:
hash:每次webpack构建时会生成一个唯一个hash值 问题:因为js和css用时使用一个hash值,如果重新打包,会导致所有缓存失效,可能当前只修改了一个文件
chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样 问题:js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk
contenthash:根据文件的内容生成hash值,不同文件的hash值一定不一样 作用:让代码上线运行缓存更好使用,推荐使用这种
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 提取css成单独文件 const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') // 压缩css process.env.NODE_ENV = 'production' // browserslist默认使用生产环境配置,这里可以手动修改为开发环境配置 // 复用loader const commonCssLoader = [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', // 需要在package.json中定义browserslist options: { ident: 'postcss', plugins: () => [require('postcss-preset-env')()] } } ] module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, enforce: 'pre', // 一般情况下,一个文件只能被一个loader处理,当一个文件需要被多个loader处理,需要指定loader的先后顺序:先执行eslint再执行babel 原因:eslint语法检查错误后面的事再去做就没有意义;babel是将es6转为es5,如果先执行eslint,转化后的var会被eslint报错 loader: 'eslint-loader', // 在package.json中定义eslintConfig options: { fix: true } // 自动修复eslint的错误 }, { // oneOf表示以下loader只会匹配一个,优化生成环境打包构建速度 注意:不能有两个loader处理一种类型文件,将eslint-loader放到外面 oneOf: [ { test: /\.css$/, use: [...commonCssLoader] }, { test: /\.less$/, use: [...commonCssLoader, 'less-loader'] // 执行过程:less-loader将less文件编译成css文件,postcss-loader对css做兼容性处理,css-loader将css加载到js中,MiniCssExtractPlugin.loader提取css为单独文件 }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', // 按需加载 corejs: { version: 3 }, targets: { chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17' } } ] ], cacheDirectory: true // 开启babel缓存,第二次构建时会读取之前的缓存 } }, { test: /\.(jpg|png|gif)$/, loader: 'url-loader', options: { limit: 6 * 1024, esModule: false, outputPath: 'images', // 打包完后css引入的图片路径会有问题 // publicPath: '../images', // 加了这句,css的图片引入没有问题,但是html中的图片引入有问题 name: '[hash:10].[ext]' } }, { test: /\.html$/, loader: 'html-loader' // 处理html中的img,需要关闭ES6的模块化 esModule: false }, { exclude: /\.(js|css|less|html|jpg|png|jif)$/, loader: 'file-loader', options: { outputPath: 'media' } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }), new MiniCssExtractPlugin({ filename: 'css/built.[contenthash:10].css' }), // 提取css成单独文件 new OptimizeCssAssetsWebpackPlugin() // 压缩css ], mode: 'production', // 生成模式下js自动压缩devServer: devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true }, devtool:'source-map' }
需要启动本地服务器:server.js:
/* 服务器代码: 启动服务器指令: npm i nodemon -g nodemon server.js node server.js 访问服务器地址: http://localhost:3000 */ const express = require('express') const app = express() app.use(express.static('build', { maxAge: 1000 * 3600 })) // express.static() 向外暴露静态资源 maxAge:资源缓存的最大时间,单位ms app.listen(3000)
tree shaking:
code split:
第一种:
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // entry: './src/js/index.js', // 单入口 entry: { // 多入口:一个入口对应一个bundle index: './src/js/index.js', test: './src/js/test.js' }, output: { filename: 'js/[name].[contenthash:10].js', // [name] 取文件名 path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }) ], mode: 'production' }
打包后的文件有几个入口文件,就有几个bundle:
第二种:
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // entry: './src/js/index.js', // 单入口 entry: { // 多入口:一个入口对应一个bundle index: './src/js/index.js', test: './src/js/test.js' }, output: { filename: 'js/[name].[contenthash:10].js', // [name] 取文件名 path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }) ], // 1、对于单入口文件(单页面应用)自动将node_modules中代码单独打包一个chunk输出 2、对于多入口文件(多页面应用)自动分析多入口chunk中有没有公共的文件,如果有会打包成单独的一个chunk optimization: { splitChunks: { chunks: 'all' } }, mode: 'production' }
第三种:使用单入口,实际开发中以单页面为主,通过js代码让某个文件单独打包成一个chunk,不同js文件中引入同一个模块只会打包成一个js文件
entry: './src/js/index.js', // 单入口 optimization: { splitChunks: { chunks: 'all' } },
/* 通过js代码,让某个文件被单独打包成一个chunk import动态导入语法:能将某个文件单独打包 */ import(/* webpackChunkName: 'test' */'./test') .then(({ add, count }) => { console.log(add(5, 6)) console.log(count(5, 6)) }) .catch(() => { console.log('文件加载失败') })
lazy loading:
webpack.config.js:
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/js/index.js', output: { filename: 'js/[name].[contenthash:10].js', path: resolve(__dirname, 'build') }, plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })], optimization: { splitChunks: { chunks: 'all' } }, mode: 'production' }
index.js中开启懒加载和预加载:
console.log('index被加载了') // import { add } from './test' document.querySelector('.btn').onclick = function () { /* 懒加载:当这个文件需要使用时才加载 预加载:prefetch,在使用之前,提前加载js 正常加载可以认为是并行加载,同一时间加载多个文件,而预加载是等其他资源加载完毕,浏览器空闲了再偷偷加载资源 webpackChunkName: 'test'----给打包后的js文件命名 webpackPrefetch: true----开启预加载 */ import(/* webpackChunkName: 'test',webpackPrefetch: true */ './test').then( ({ add, count }) => { console.log(add(5, 6)) console.log(count(5, 6)) } ) }
pwa:
1、渐进式网络开发应用程序,离线可访问。下载插件:npm i workbox-webpack-plugin -D
2、webpack.config.js中引入插件和使用插件:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin') new WorkboxWebpackPlugin.GenerateSW({ // 生成一个serviceWorker配置文件 clientsClaim: true, // 帮助serviceWorker快速启动 skipWaiting: true // 删除旧的serviceWorker })
3、src/index.js中注册serviceWorker:
// 注册serviceWorker if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('/service-worker.js') .then(() => { console.log('sw注册成功') }) .catch(() => { console.log('sw注册失败') }) }) }
4、eslint不支持window、navigate 等全局变量,需要在package中"eslintConfig"中加上:
"env": { "browser": true }
5、sw代码必须运行在服务器上,一种是通过nodejs,一种是通过serve,这里用serve的方式:
npm i serve -g
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
多进程打包:
1、下载插件:npm i thread-loader -D
2、使用:
{ test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'thread-loader', // 开启多进程打包,多进程本身启动大概为600ms,进程通信也有开销,只有工作消耗时间较长,才需要多进程打包 options: { workers: 2 } // 进程2个 }, { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ], cacheDirectory: true } } ] },
externals:
如果使用了外部cdn资源,则在打包的时候拒绝该资源被打包进来,提高打包速度----需要cdn引入的包,需要在externals中拒绝打包,在html中通过script标签引入cdn资源
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.js', path: resolve(__dirname, 'build') }, module: { rules: [] }, plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })], mode: 'production', externals: { jquery: 'jQuery' } // 拒绝jQuery被打包进来 }
externals中拒绝某个包的前提是在html中引入了cdn资源:<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
dll:
1、下载插件:npm i add-asset-html-webpack-plugin -D
2、定义webpack.dll.js:
/* 使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包 当运行webpack时,默认查找 webpack.config.js 配置文件 需求:需要运行 webpack.dll.js 文件 webpack --config webpack.dll.js */ const { resolve } = require('path') const Webpack = require('webpack') module.exports = { entry: { jquery: ['jquery'] // 最终打包生成的[name] --> jquery ['jquery'] --> 要打包的库是jquery }, output: { filename: '[name].js', path: resolve(__dirname, 'dll'), library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字 }, plugins: [ // 打包生成一个 manifest.json 提供jquery映射 new Webpack.DllPlugin({ name: '[name]_[hash]', // 映射库的暴露的内容名称 path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径 }) ], mode: 'production' }
运行 webpack --config webpack.dll.js
3、webpack.config.js:
const { resolve } = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const Webpack = require('webpack') const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin') module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.js', path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), new Webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest.json') }), // 告诉webpack哪些库不参与打包,同时使用的名称也得变 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, 'dll/jquery.js') }) // 将某个文件打包输出去,并在html中自动引入该资源 ], mode: 'production' }
注意:
如果用cdn引入第三方资源,用externals;
如果需要将第三方库进行单独打包,不使用cdn连接,通过自己服务器向外暴露出去,用dll。
x