提取css成单独文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| plugins: [ new MiniCssExtractPlugin({ filename: 'css/build.css' }) ], module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader'
] } ] },
|
压缩css
1 2 3 4 5 6 7
| const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
optimization: { minimizer: [ new CssMinimizerWebpackPlugin() ] },
|
babel-loader
将js文件中es6打包转为所有浏览器都能识别的es5
安装
1
| npm install -d babel-loader @babel/core @babel/preset-env
|
- babel-loader 在webpack里应用babel解析es6的桥梁
- @babel/core babel核心模块
- @babel/preset-env babel预设,一组babel插件的集合
初次之外 还需要regeneratorRuntime插件,这是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await语法
1 2 3 4 5
| npm install @babel/runtime -d
npm install @babel/plugin-transform-runtime -d
|
最终在webpack.config.js中配置为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: [ [ '@babel/plugin-transform-runtime' ] ] } } }
|
代码分离
用来获得更小的bundle,使bundle按需加载或者并行加载,常用的有三种方法
入口起点
使用entry配置手动的分离代码
缺点:如果是多个入口,那么每个入口中共享的文件会分别在每个包里重复去打包
1 2 3 4 5 6 7 8 9 10 11 12 13
| entry: { index: './src/index.js', another: './src/another.js' },
output: { filename: '[name].bundle.js', path: path.resolve(__dirname, './dist'), clean: true,
assetModuleFilename: 'image/[contenthash][ext]', },
|
这样会生成两个打包文件, index.bundle.js 和 another.bundle.js
防止重复
使用Entry dependencies 或者 SplitChunksPlugin 去重和分离代码
Entry dependencies
1 2 3 4 5 6 7 8 9 10 11 12 13
| entry: { index:{ import:'./src/index.js', dependOn:'shared' }, another:{ import:'./src/index.js', dependOn:'shared' },
shared:'less' },
|
比如两个js文件均引入了less模块:import less from ‘less’,如果普通的打包方式两个打包后的文件分别大小,分别为1.32M 和 1.2M.如下
如果改为这种防止重复的方法则共有三个打包文件,如下
其中shared.bundle.js是打包共享模块的打包文件,另外两个明显缩小
SplitChunksPlugin
1 2 3 4 5 6 7 8 9
| entry: { index: './src/index.js', another: './src/another.js', }, optimization: { splitChunks: { chunks: 'all' } },
|
这样会自动抽离公共模块生成额外两个打包文件
动态导入
通过模块的内联函数调用来分离代码.使用了async提供的import的函数
新建一个async-module.js文件,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function getComponent() { return import('lodash').then(({ default: _ }) => { const element = document.createElement('div') element.innerHTML = _join(["hello", "webpack"], ' ') return element }) }
getComponent().then((element) => { document.appendChild(element) })
|
webpack.config.js如下,仅仅使用最简单的方式打包
1 2 3 4
| entry: { index: './src/index.js', },
|
在index.js中引入async-module.js
这样打包后会单独分离出一个lodash的打包文件
动态导入和前面的静态方法可以一起使用
动态导入的应用
懒加载
又称为按需加载.懒加载是动态导入代码分离方法的一种应用
因为有一些模块应用的很少,或者根本不会被引用.所以这些时候可以在需要的是在才对这些模块进行加载,从而得到优化
新建一个math.js文件如下
1 2 3 4 5 6 7
| export function add(x, y) { return x + y }
export function sub(x, y) { return x - y }
|
在index.js中新增一个按钮,用来加载math.js并调用其中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const button = document.createElement('button') button.textContent = "点击执行10+10" button.addEventListener('click', () => { import('./math').then(({ add }) => { console.log(add(10, 10));
}) })
|
这样打包后会生成一个新的单独的math的打包文件,其中包含了math.js中定义的两个方法,从而在需要这个包的的时候才会去加载
这时在浏览器中初启动时并不会请求加载math打包的相关文件
当点击按钮后
这时才会去请求加载math的打包文件
预获取和预加载
预获取
在下一个页面即将用到的资源可以在父页面中进行预加载,他会在父页面所有其他资源加载完毕后,在浏览器网络空闲时进行下载.
1 2 3 4 5 6
| button.addEventListener('click', () => { import('./math').then(({ add }) => { console.log(add(10, 10)); }) })
|
预加载
1 2 3 4 5 6
| //这样可以自定义生成的打包文件的名称,这样math文件打包出来的名字为math.bundle.js button.addEventListener('click', () => { import(/*webpackChunkName:'math',webpackPreload:true*/'./math').then(({ add }) => { console.log(add(10, 10)); }) })
|
在资源上添加预先加载的注释,你指明该模块需要立即被使用。异步chunk会和父级chunk并行加载。如果父级chunk先下载好,页面就已可显示了,同时等待异步chunk的下载。这能大幅提升性能。
拆分配置文件及合并
新建config文件夹
新建webpack.config.common.js这里面写开发环境和生产环境公共的配置
不同的是:
- output中的filename:在开发环境中由于不需要浏览器的缓存功能,所以不需要每次更新打包文件都更改打包名称,所以在开发环境中固定打包名称,在生产环境中加入hash使每次更新打包文件都声称新的不同的名称,触发浏览器更新缓存
- output中的publicPath:在开发环境中只用于测试,无需设置公共路径.但是在生产环境中需要设置公共路径localhost:8080/
- devtool:在开发环境中需要堆代码进行调试,查找报错位置,需要devtool直接定位到错误位置
- devServer:在开发环境中要自动生成html文件并在修改代码时是页面自动更新,即npx webpack-dev-server
- mode:模式
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
|
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const { node } = require('webpack')
module.exports = { entry: { index: './src/index.js', another: './src/another.js', },
output: { path: path.resolve(__dirname, '../dist'), clean: true, assetModuleFilename: 'image/[contenthash][ext]', },
plugins: [ new HtmlWebpackPlugin({ template: './index.html', filename: 'app.html', inject: 'body' }), new MiniCssExtractPlugin() ],
module: { rules: [ { test: /\.(png|jpg|gif)$/, type: 'asset/resource', generator: { filename: 'image/[contenthash].[ext]' } }, { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ] }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.png$/, type: 'asset/resource', generator: { filename: 'image/[contenthash][ext]' } }, { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: [ [ '@babel/plugin-transform-runtime' ] ] } } }
] },
optimization: { splitChunks: { cacheGroups: { vendors: { test: '/[\\/]node_modules[\\/]/', name: 'vendors', chunks: 'all' } } } },
}
|
新建webpack.config.dev.js 这里配置开发环境中需要的特有配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| module.exports = {
output: { filename: 'js/[name].js', },
devtool: 'inline-source-map',
devServer: { compress: true, port: 8080, static: './dist', open: true }, mode: 'development', }
|
新建webpack.config.pro.js 这里配置生产环境中需要的特有配置
1 2 3 4 5 6 7 8 9 10 11
| module.exports = {
output: { filename: 'js/[name].[contenthash].js', publicPath: 'localhost:8080/', }, mode: 'production', }
|
下载webpack-merge包用于合并配置
新建webpack.config.js文件,在其中对命令进行判断,从而决定怎么合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const { merge } = require('webpack-merge') const commonConfig = require('./webpack.config.common') const productionConfig = require('./webpack.config.pro') const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => { switch (true) { case env.development: return merge(commonConfig, developmentConfig) case env.production: return merge(commonConfig, productionConfig) default: return new Error('没有匹配到env环境') } }
|
在package.json中
1 2 3 4
| "scripts": { "start": "npx webpack server -c ./config/webpack.config.js --env development", "build": "npx webpack -c ./config/webpack.config.js --env production" },
|
source-map
webpack内置source-map,由于浏览器加载的是打包好的bundle.js文件,所以在报错定位时查看到的也是bundle文件.上面也已经用到了
1
| devtool: 'inline-source-map',
|
source-map有好多种可选值,当不设置的时候默认为eval
- eval 将每一个module封装到eval里包裹 起来执行,并且会在末尾加注释//@sourceURL
- source-map 生成source-map 的打包文件
devServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| devServer: { static: path.resolve(__dirname, '../dist'), compress: true, port: 8080, open: true,
headers: { 'X-Access-Token': 'abc123' } proxy: { '/api': 'http://localhost:9000' }, historyApiFallback:true }
|
优化开发环境
优化打包构建速度
oneOf
每个文件都会被rules中的所有loader匹配处理,使用oneOf则在oneOf中的loader只会匹配一个,提高构建速度.但是注意不能有两个配置处理同一类型的文件.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| module: { rules: [ { oneOf: [ { test: /\.(png|jpg|gif)$/, type: 'asset/resource', generator: { filename: 'image/[contenthash].[ext]' } }, { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ] }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ], }, { test: /\.png$/, type: 'asset/resource', generator: { filename: 'image/[contenthash][ext]' } }, { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: [ [ '@babel/plugin-transform-runtime' ] ] } } } ] }
] },
|
tree shaking
去除无用代码,即使引用了包但是没有使用也会自动去除.但是不能百分百的tree shaking
前提:必须使用ES6模块化,开启production环境
1 2 3 4
| mode:'production', optimization: { usedExports: true },
|
sideEffects
有些引入的包虽然没使用但是有用,而有些不是,使用sideEffects可以告诉webpack哪些包是必要的不能tree shaking.
在package.json中设置sideEffects,共有三个可选值
- true 我的所有代码都是有用的,不能随意去除
- false 我所有的包都是没有用的,那么一些引入的包,但是代码中并没有使用的话就会被去除(比如引入的css)
- [] 可以使用一个数组定义哪些有用,不能删除(如 sideEffects:[“*.css”] )
模块热替换(HMR)与热加载
模块热替换
模块热替换会在应用程序运行过程中,替换,添加或删除模块,而无需重新加载整个页面.开启方式只需在devServer下配置hot:true即可.
比如当更改css文件的某一个背景颜色时,不开启热替换的话需要刷新页面才能显示新的页面.但是如果开启了热替换的话则无需刷新页面直接自动更新.
不过js文件即使开启了热替换也不能进行自动替换,仍会刷新界面才能展示新的效果.这时需要手动进行判断并配置
比如input.js如下
1
| document.querySelector('#box').innerHTML = '<input type="text" value="input1"/>'
|
在入口文件中引入input.js,进行如下配置
1 2 3 4 5
| if (module.hot) { module.hot.accept('./input.js', () => {
}) }
|
这样如果在input.js中改变value的值的话,就能进行自动热替换了.vue中已经自动帮助我们做好了
热加载
文件更新时自动刷新服务和页面.新版的webpack-dev-server默认已经开启了热加载的功能,他对应的参数是devServer.liveReload,默认为true.注意:如果想要关掉他,要将liveReload设置为false的同时,也要关闭hot.
模块解析(resolve)
在resolve中设置alias可以设置别名.extensions可以在当前路径中存在文件名相同,扩展名不同的文件,按照extensions配置的顺序优先解析
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 31 32
| const HtmlWebpackPlugin = require('html-webpack-plugin') const path = require('path') module.exports = { mode: 'development', entry: './src/app.js', resolve: { alias: { '@': path.resolve(__dirname, './src') }, extensions:['.json','.js','.vue'] }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './index.html' }) ], devServer: { hot: true } }
|
externals
externals
配置选项提供了「从输出的 bundle 中排除依赖」的方法。
多页面
entry配置
将部分不相关的js文件打包成一个文件
1 2 3 4
| entry: { main: ['./src/js/app.js', './src/js/app2.js'], another: './src/js/another.js' },
|
这样’./src/js/app.js’, ‘./src/js/app2.js’会打包到一个main.js中,而./src/js/another.js会在另一个中.
HtmlWebpackPlugin
HtmlWebpackPlugin中可以配置当前页面可以载入哪些chunk.这样打包好的html中只引入了main.js打包文件
1 2 3 4 5 6 7 8
| plugins: [ new HtmlWebpackPlugin({ template: './index.html', inject:'body', chunks:['main'] }) ],
|
多页面环境搭建
主要是配置两个htmlwebpackplugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| plugins: [ new HtmlWebpackPlugin({ template: './index.html', inject: 'body', filename: 'chanel1/index.html', chunks: ['main'] }), new HtmlWebpackPlugin({ template: './index2.html', inject: 'body', filename: 'chanel2/index2.html', chunks: ['another'] }), ],
|
渐进式网络应用程序(PWA)
共两步
添加Workbox
npm install workbox-webpack-plugin -d 安装workbox
1 2 3 4 5 6 7 8 9 10 11 12 13
| const WorkboxPlugin = require('workbox-webpack-plugin')
plugins: [ new HtmlWebpackPlugin({ template: './index.html', inject: 'body', filename: 'index.html', }), new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true }) ],
|
在浏览器中注册Service-Worker
在入口文件中
1 2 3 4 5 6 7 8 9 10 11 12
| if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/service-worker.js') .then(registration => { console.log('注册成功', registration); }) .catch(registrationError => { console.log('注册失败', registrationError);
}) }) }
|
经过这两步之后,就能实现即使服务器挂掉,静态页面仍能显示.
dll