output配置
- 如果把index.html给后端作为入口文件,将js文件、css文件上传到cdn,那么就希望在html中引入的文件是以cdn地址为开头,比如:cdn.com/main.js:
可以在output中设置:publicPath:”cdn.com“
output: {
filename: "[name].js", //入口文件的命名
chunkFilename: "[name].chunk.js", //间接js文件(非入口,被分割的js代码文件命名)
path: path.resolve(__dirname, "./dist")
}
复制代码
loader配置
- 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
- loader的执行顺序是从下到上,从右到左
file-loader
- 当遇到匹配的类型文件,会将其打包到dist目录下,生成一个文件,并将模块里使用到这个文件的路径修改为dist目录下的文件
- 理论上能够处理任何静态资源,即如果想让一个文件挪动到dist目录下,并且使用这个文件挪动后的地址,都可以使用file-loader对其进行打包
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'file-loader',
options: {
//指定文件名
name: '[name]_[hash].[ext]',
//指定文件的输出目录,应为相对于webpack输出目录的相对路径
outputPath: 'static/img/',
//指定请求文件时的url,生成的URL为/dist/static/img/${name}
publicPath: '/dist/static/img/'
}
}
//注意:outputPath, publicPath一定要以/结尾
复制代码
url-loader
- options里面有一个limit配置项,如果文件大小大于limit,会和file-loader一样单独打包生成一个文件放到dist目录下;如果文件大小小于limit,会将文件生成一个base64的字符串,直接放到bundle.js文件当中
css-loader
- 分析css文件之间的关系,将他们合并成一段css
- 将css转化成CommonJS模块
- 部分配置项
- importLoaders: 例如:在a.sass文件中引入b.sass文件,index.js文件中import了a.sass文件,a.sass会链式调用postcss/sass/css/style loader来处理样式;而在import文件里面又引入的b.sass文件有可能会不经过postcss/sass而是直接经过css/style loader处理了,使用importloader可以解决这个问题:相当于用@import引入的css文件也要经过postcss和sass loader的处理(无论是在js文件中还是在sass文件中引入的sass文件都会经过四个loader处理)
- css打包的模块化(modules: true) 使用: import style from ‘./index.sass’;
style-loader
- 得到css-loader生成的css内容之后,style-loader将其挂载到页面的header标签当中生成一个style标签
less-loader、sass-loader
- 将sass/less编译成css
- 使用sass-loader要安装 sass-loader和node-sass
postcss-loader
- 为css3添加样式前缀,安装postcss-loader
- 在根目录下创建postcss.config.js文件并且安装autoprefixer
//postcss.config.js
module.exports = {
plugins: [
require("autoprefixer")
]}
复制代码
即postcss-loader通过autoprefixer插件给css3样式添加了厂商前缀
使用Babel处理ES6语法
- babel
- plugins
- @babel/plugin-transform-runtime
- @babel/plugin-syntax-dynamic-import
- presets
- @babel/preset-env
- @babel/preset-react
- plugins
- 网站: babeljs.io —> setup —> webpack
- 安装: npm install –save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill
- babel-loader: 帮助webpack做打包时使用的工具
- @babel/core: 是babel所有的逻辑源文件;babel的核心库,能让babel去识别js代码里的内容,将js代码转化成AST抽象语法树,再将抽象语法树编译转化成一些新的语法出来
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
复制代码
- 根目录创建.babelrc文件
- 当使用babel-loader处理js文件时,babel-loader只是babel和webpack通信的桥梁,只是起到类似通信的作用,不做具体转换;而实际做转化的模块就是 babel/preset-env
- babel/preset-env里面包含了所以es6转化成es5的规则
// 对babel/preset-env的配置:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{presets:["@babel/preset-env"]}
}
]
}
// 或者在.babelrc文件中:
{
"presets": ["@babel/preset-env"]
}
复制代码
- 单单进行es6向es5转换是不够的,还有一些对象、函数在低版本是不适用的,这部分使用babel/polyfill: 给低版本做变量、函数的补充
在项目代码入口文件顶端:import "@babel/polyfill";
(设置了useBuiltIns之后会自动引入,不用再手动引入)
如果想只对用到的一些变量和函数进行补充而不是全部,可进行以下配置:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{presets:[
//当用babel/polyfill填充的时候,不将所有的特性都加进来,
// 而是根据业务代码的使用情况来补充,缩小输出文件体积
//支持到chrome 67以上版本
["@babel/preset-env",targets:{chrome:"67"},{useBuiltIns:"usage"}]
]}
}
]
}
复制代码
- 如果是开发一个组件库时,使用babel/polyfill在项目入口文件注入,在注入如promise/map是通过全局变量的方式进行注入,会污染全局环境,所以需要换一种配置方式:
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime(npm install --save @babel/runtime-corejs2)
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
}
]
]
}
复制代码
- 小结:
如果写的是业务代码,配置的时候配置presets同时引入babel/polyfill;
如果写的组件库项目代码时:使用plugins配置,babel/plugin-transform-runtime插件,这个插件的好处是可以有效地避免polyfill会污染全局环境的问题,这个插件会以闭包的形式来引入必要的内容;所以这个方案更合理
React代码的打包
npm install --save-dev @babel/preset-react
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{presets:["@babel/preset-env", "@babel/preset-react"]}
}
]
}
复制代码
plugins
- 插件目的在于解决 loader 无法实现的其他事。可以在webpack运行到某个时刻的时候帮忙做一些事情
- HtmlWebpackPlugin
- CleanWebpackPlugin
- webpack.namedModulesPlugin 热更新时打印文件名称
- webpack.HotModuleReplacementPlugin 热更新
- mini-css-extract-plugin
- optimize-css-assets-webpack-plugin
HtmlWebpackPlugin
- 会在打包结束后自动生成一个html文件(默认模板或者指定模板),并把打包生成的js文件自动引入到这个html文件中
- 如果想让生成的html文件自动包含一个id为“root”的div,可以配置template参数,给他一个html模板
// 自动生成html文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
title: "页面名称",
template: "src/index.html"
})
]
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title></head><body>
//增加
<div id="root"></div>
</body></html>
复制代码
CleanWebpackPlugin
- 在每次构建前清理 /dist 文件夹,这样只会生成用到的文件
- 修改配置文件,告知 CleanWebpackPlugin 你不想在 watch 触发增量构建后删除 index.html 文件,可以通过配置 cleanStaleWebpackAssets 选项 来实现
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
plugins: [
new HtmlWebpackPlugin({
title: '管理输出',
}),
new CleanWebpackPlugin({
cleanStaleWebpackAssets: false
// 添加这个参数是为了监听到代码改变时,不会把没有改变的文件清除
}),
]
复制代码
webpack.ProvidePlugin 自动加载模块,而不必到处 import 或 require
plugins:[
new webpack.ProvidePlugin({
"$":"jquery",
"_join":["lodash","join"],//定义lodash里的具体某一个方法
})
]
复制代码
自动编译
- webpack提供了几种可选方式,帮助你在代码发生变化后自动编译代码,而不用每次都手动执行npm run build
- webpack watch mode(webpack 观察模式):唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器
- webpack-dev-server: dist目录下不会生成打包的文件
- 提供了一个简单的web-server,并且具有live reloading(实时重新加载)功能
- webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。如果你的页面希望在其他不同路径中找到 bundle 文件,则可以通过 dev server 配置中的 publicPath 选项进行修改。
- webpack-dev-middleware
- webpack-dev-middleware 是一个封装器(wrapper),它可以把 webpack 处理过的文件发送到一个 server。
- webpack-dev-server 在内部使用了它,然而它也可以作为一个单独的 package 来使用,以便根据需求进行更多自定义设置
"scripts": {
"build": "webpack --config webpack.config.js",
"watch": "webpack --watch",
"start": "webpack-dev-server --open",
"server": "node server.js"
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
//我们将会在 server 脚本使用 publicPath,
//以确保文件资源能够正确地 serve 在 http://localhost:3000 下
publicPath: '/',
},
// server.js
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const app = express();
const config = require("./webpack.config2.js");
const compiler = webpack(config);
/*告知express 使用 webpack-dev-middleware,以及将webpack.config.js配置文件作为基础配置*/
app.use(webpackDevMiddleware(compiler, {publicPath:config.output.publicPath}));
// 将文件serve到port 3000
app.listen(3000, () => {console.log("listening on port 3000...")});
复制代码
HMR热模块替换(hot module replacement)
- 它允许在运行时更新各种模块,而无需进行完全刷新。
- 方便调试css样式
- 热更新js要手动刷新
devServer: {
//开启热替换
hot:true,
//顺带也会配置上hotOnly,可以理解为即使HMR没有生效,也不让浏览器自动刷新
hotOnly:true
}
const webpack = require("webpack");
plugins: [
//在热加载时直接返回更新文件名,而不是文件的id
new webpack.NamedModulesPlugin(),
//热更新插件
new webpack.HotModuleReplacementPlugin()
]
复制代码
css-loader、vue-loader已经写了这部分代码,所以不需要手写了,但是如果是HTML,需要自己手动执行下面的代码
if (module.hot) {
module.hot.accept('文件地址', () => {
// 重新执行文件
})
}
这段代码的意思是:判断项目中是否启用了module.hot模块,如果启用了,就执行if语句中的代码
这个函数接收两个参数,第一个是文件的名字,第二个是回调函数,也就是当监听的文件发生变化时,
就执行回调函数;我们可以在回调函数中做一些处理;
这就是HMR实现的原理,由于webpack中的样式处理模块(css-loader、less-loader)已经实现了底层处理,
因此只要开启了HMR就能够实现CSS的自动更新,但是JS的loader并没有实现,因此需要手动去实现;
复制代码
tree-shaking
- 只支持ES Module 模块的引入 :如 import {input} from ‘./index.js’;【底层静态引入】
- (common js的引入方式是不支持的:const add = require(‘./index.js’)【底层动态引入】 );
- development模式下是没有tree shaking这个功能的:
- 在development模式下使用treeshaking,不会将没有使用的代码从输出文件中删除,只是在打包文件中提示exports provided:add, minus; exports used: add;因为在开发环境下会做代码调试,如果打包删除代码,source-map对应的行数可能就不对了。
//development模式下
optimization: {
//看哪些导出的模块被使用了,再做打包
usedExports: true
}
复制代码
在package.json里增加配置:有的文件可能引入了但是没有导出任何的内容,但是是有效文件,(如@babel/poly-fill)为了防止tree shaking将其删除
//false即对所有文件都正常treeshaking
"sideEffects":false,
//禁止treeshaking的文件写入数组当中,(css文件)
"sideEffects":["@babel/poly-fill","*.css"]
复制代码
- 在production模式下:treeshaking的一些配置自动开启,无需手动配置,package.json下的sideEffect还是要设置
webpack和code splitting(代码分割)
打包文件大,加载时间长,codespliting 提高代码执行效率,提升用户体验
如将一个文件拆分成两个文件,页面在加载的时候可以并行请求,速度可能会比加载一个大体积文件要快;另一个方面,当其中一个文件发生修改的时候,浏览器因为有缓存,只需要加载修改的文件即可,提高了加载速度
- 利用webpack进行代码分割:
- 同步的代码进行代码分割,公用的类库单独生成一个文件;业务代码生成一个文件
- 异步代码:会自动进行代码分割
optimization:{
splitChunks: {
chunks: "all"
}
}
复制代码
function getComponent(){
return import('lodash').then(({default: _}) => {
const element = document.createElement("div");
element.innerHTML = _.join(['hello','world'],"--");
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
复制代码
-
对于动态import语法需要借助babel来进行编译:npm i babel-plugin-dynamic-import-webpack –save-dev
-
小结: 在webpack里的代码分割两种方式
- 在webpack里进行optimization配置结合编写常见的同步代码
- 编写异步代码
-
splitChunksPlugin配置参数
- webpack的代码分割底层使用了splitChunksPlugin这个插件
- 动态导入的库生成一个叫做0.js的文件,这是codespliting代码分割产生的一个id的值
import(/* webpackChunkName:"lodash"*/'lodash')
//意思是异步引入lodash库,当单独对这个库进行打包时,打包生成的文件名称叫做lodash
//使用魔法注释语法之后,重新打包发现文件名还是叫0.js
//是因为在上面用于解析动态引入的插件babel-plugin-dynamic-import-webpack并不识别这种魔法注释的语法
//这不是官方推荐的插件
//将其换成官方推荐的 npm i --save-dev @babel/plugin-syntax-dynamic-import
.babelrc中
{
"presets": [
[
"@babel/preset-env",
{
"target": { chrome: 76},
"useBuiltIns": "usage"
}
],
"@babel/preset-react"
],
- "plugins": ["dynamic-import-webpack"]
+ "plugins":["@babel/plugin-syntax-dynamic-import"]
}//重新打包:生成的文件叫做:vendors~lodash.js
//如果想让名字和魔法注释里的一模一样,要修改splitchunksplugin的配置
复制代码
optimization:{
splitChunks: {
chunks: "all",
cacheGroups: {
vendors: false,
default: false
}
}
}
// 这样打包出来的文件就叫做lodash.js;
// 以上的配置项不论是对同步代码还是异步代码都会起作用,即代码分割都用到了splitchunksplugin插件
复制代码
- 当把splitchunks设置成空对象,会采用默认配置;默认配置如下:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: { //代码分割的默认分组
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
+ filename:"common.js"
}
}
}
/**
chunks:
“async”对异步代码进行代码分割
“all”对同步异步代码都进行代码分割
“initial”表示对同步代码进行代码分割
配置成all时要结合cacheGroup里的配置才能实现对同步代码进行代码分割,
检测要分割的同步代码是否属于vendors这个组(是否存在于node_modules里),lodash符合,
则打包出来的文件叫做vendors~main.js(mian表示是以main.js作为入口)
如果希望打包的文件名就叫做vendors。js文件当中可以加一个配置项:
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
+ filename: "vendors.js"
},、
}
minSize:大于30kb的文件才去做代码分割
maxSize:如果文件过大,会按照设置的值对文件进行二次分割,不常用
minChunks:当一个文件至少用了多少次,才对其进行代码分割(打包生成的js文件中有哪些用到了某个文件)
maxAsyncRequests: 同时加载的模块数最多是几个;假如引入了10个类库就分割成了10个文件,
这样同时要加载10段代码,违反了maxAsyncRequests为5的要求;
webpack遇到这种会在打包前5个库的时候生成5个文件,超过5个就不会再做代码分割了
maxInitialRequests:整个网站首页(入口文件)加载时,引用的库做代码分割最多是几个
automaticNameDelimiter:生成的文件名中间的连接符
name:true让cacheGroup里面定义的filename生效
cacheGroup中:
priority值越大优先级越高 决定文件放在哪个组当中
reuseExistingChunk: 如果一个模块已经被打包到某个组里,再打包时就忽略这个模块,直接复用
*/
复制代码
lazy loading懒加载
- 利用import语法动态加载需要的组件
//两种写法
function getComponent(){
return import('lodash').then(({default: _}) => {
const element = document.createElement("div");
element.innerHTML = _.join(['hello','world'],"--");
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
async function getComponent(){
const {default:_} = await import(/* webpackChunkName:"lodash"*/'lodash')
const element = document.createElement("div");
element.innerHTML = _.join(['hello','world'],"--");
return element;
}
getComponent().then(element => {
document.body.appendChild(element);
})
复制代码
- 对于jquery/lodash这种库文件进行代码分割,实际上是利用浏览器缓存提升二次访问的性能,对于第一次的访问的性能的提升帮助并不是很大
- 推荐的代码写法:尽可能使用异步代码的写法 提升首次加载性能
preloading/prefetching
- prefetch:当主要js加载完成之后,网络带宽有空闲的时候,去加载这个文件
- preload:文件和主业务文件一起加载
/*webpackPrefetch:true*/
/*webpackPreload:true*/
document.addEventListener('click', () => {
import(/*webpackPrefetch:true*/"./click.js").then(({default:func}) => {
func();
})
})
复制代码
css文件的代码分割 —MiniCssExtractPugin(production环境)
- 线上环境的打包使用MiniCssExtractPugin,因为暂时不支持HMR,所以更新的css样式不会自动更新在页面,需要手动刷新,所以在开发环境下使用会不方便
const MiniCssExtractPugin = require("mini-css-extract-plugin");
plugins:[
new MiniCssExtractPugin({
filename:"[name].css",
chunkFilename:"[id].css"
})
]
{
test:'/\.css$/',
loader: [
- 'style-loader',
+ MiniCssExtractPlugin.loader
{
loader: "css-loader",
options: {
importLoaders: 2,
modules: true
}
},
'less-loader',
'postcss-loader'
]
}
复制代码
会将css代码合并到一个文件进行打包输出、
如果相对输出的css文件进行压缩和合并:可以使用
optimize-css-assets-webpack-plugin
optimize-css-assets-webpack-plugin底层也会使用到splitChunksPlugin;假如项目中有多个入口文件,通过minicssextractplugin会分割出多个css文件,如果想将这些css文件合并打包在一个文件当中,可以在cacheGroup里面增加一个分组:
optimization:{
splitChunks:{
cacheGroups:{
styles:{
name:"styles",
test:/\.css$/,
chunks:"all",
enforce:true,//忽略掉splitchunks其他的默认配置项
}
}
}
}
复制代码
注意点
原文链接:blog.csdn.net/weixin_4466…
webpack官方文档介绍时并不是将 「OptimizeCssAssetsWebpackPlugin」 插件配置在「plugins」数组中。而是配置在 「optimization.minimizer」 数组中。原因是:
配置在「plugins」中,webpack就会在启动时使用这个插件。
而配置在 「optimization.minimizer」 中,就只会在「optimization.minimize」这个特性开启时使用。
所以webpack推荐,像压缩类的插件,应该配置在「optimization.minimizer」数组中。
以便于通过「optimization.minimize」统一控制。(生产环境会默认开启minimize)
然而这样配置会导致JS不会被压缩。
原因是webpack认为,如果配置了minimizer,就表示开发者在自定义压缩插件。
内部的JS压缩器就会被覆盖掉。所以这里还需要手动将它添加回来。
webpack内部使用的JS压缩器是「terser-webpack-plugin」。
注意:手动添加需要安装这个插件才能使用。
webpack与浏览器缓存(caching)
- 配置contenthash帮助浏览器合理的做缓存
output: {
//入口文件的命名
filename: "[name].js",
//间接js文件(非入口,被分割的js代码文件命名)
chunkFilename: "[name].chunk.js",
path: path.resolve(__dirname, "./dist")
}
复制代码
这样的配置只适合在开发环境,如果在生产环境中,改变代码重新打包发布上线,由于文件名称没有变化,浏览器有可能直接拿缓存进行渲染,这样就看不到更新的内容
为了解决这个问题,使用contenthash;对于变化的文件,需要重新去服务器请求,没有变化的文件依旧使用浏览器缓存
output: {
filename: "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js",
path: path.resolve(__dirname, "./dist")}
}
复制代码
对于旧版本的webpack,有可能没有改变代码,两次打包后文件的hash值还是发生了变化,解决这个问题需要额外增加以下配置:
optimization:{
runtimeChunk:{
name:"runtime"
}
}
复制代码
这样设置之后打包出来的文件会多出一个 runtime.(hash值).js文件。在做webpack打包的时候,main.js放的是业务逻辑,vendors.js放置的是库,业务逻辑和库之间也是有关联的,这之间的关联代码叫做manifest,默认manifest是存在于main.js里面,也存在于vendors.js里面;manifest在旧版本的webpack中每次打包的时候可能会有差异,导致没有改变源代码的情况下contenthash值也发生了变化
当配置了runtimechunk,就把manifest关系相关的代码抽离出来单独放置在runtime文件当中
环境变量的使用
+ --env.production
"script":{
"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.common.js中
const merge = require("webpack-merge");
module.exports = (env) => {
if(env && env.production){
//生产环境
return merge(commonConfig, prodConfig);
}else{
//开发环境
return merge(commonConfig, devConfig);
}
}
复制代码