webpack 4 慕课网全套课程学习笔记
1、安装webpack-cli会自动帮我们安装webpack
2、npx webpack index.js 让webpack帮我们翻译index.js(会帮我们翻译和链接ES6的模块,翻译成浏览器能认识的代码)
3、webpack初步认识:JS代码的翻译器。webpack只认识import这样语句,其他高级JS语法它一概不认识。所以称作JS代码的翻译器是高看了它。实际上,webpack的核心定义是模块打包工具。把不同的模块打包到一起。
4、webpack对符合ES Module规范模块、CommonJS规范模块、CMD、AMD模块都能正确认识。
5、webpack发展:纯JS模块打包工具—》css,scss, png, jpg等模块打包
6、npm init完成后配置package.json:
增加“private“:true 属性,说明该项目是私有项目,npm就不会放到共有云上去
去掉”main”: index.js字段,因为我们的项目不会被别人引用,只是自己来用,没有必要向外暴露一个JS文件
7、有时候安装webpack不成功,是因为npm源被国内墙掉了,解决问题方法:用手机分享WiFi热点,再去安装就没问题了。不推荐全局安装webpack(npm install webpack webpack-cli -g),会无法同时使用两个版本的webpack。推荐局部安装(npm install webpack -D // -D 等同于 —save-dev),局部安装无法直接执行webpack命令(如webpack -v), 我们可以执行npx webpack -v, npx会帮我们在当前目录的下node_moudles的包。需要webpack-cli才能支持在命令行执行webpack命令。
8、npm info webpack或其他包:可以看到某个包有哪些版本,npm install [email protected]安装指定webpack版本。
9、webpack默认配置文件:webpack.config.js。 配置中output.path:定义打包出来的文件存放在哪里,必须是绝对路径,如 path.resolve(__dirname, ‘bundle’)。
output.path定义的绝对路径,是整个工程所有打包输出文件的存放根目录。
不同资源的loader配置的options.outputPath定义的相对路径,是相对根目录output.path的存放路径。
10、webpack打包命令行结果提示中的chunks 指的是chunk的ID值,后面的chunk name指的是名称,默认main。因为entry:’./src/index.js‘ 实际上是 entry: { main: ‘./src/index.js’ } 的简写。
webpack的默认模式mode是production,打包出的文件会被压缩。mode的另一个值是development,打包出的文件不会被压缩。
11、webpack官方推荐的loaders和plugins都有很多,配置也有很多。加上个人和机构开发的,有几万个,想全都学会,基本不可能,所以需要套路,学会核心的东西,在实践当中,去查看文档。当然这个方法还不是很好,更好的方法是群里发出来讨论,不用查,直接抛出来,因为每个人处理的业务场景不同,这样大大提高开发效率。
12、webpack只认识JS文件的打包,其他类型的文件需要在module: { rules: [{},{}…] }配置里面告诉webpack怎么处理。
13、webpack遇到图片文件,会求助file-loader, 这个loader会把图片挪到指定的dist目录,然后改个名称(名称可以配置),再返回新的名称给webpack。根据这个原理,file-loader可以打包任何的静态文件,如svg, excel,txt文件,因为静态文件的打包处理大都是直接复制到打包输出目录。
14、var img = new Image() // 这句代码创建了一个image标签
img.src = require(‘./avatar.png’) // 图片经过file-loader处理,返回图片在dist目录的hash名称
img.classList.add(‘avatar’) // 添加样式class
document.getElementById(‘root’).append(img) // 图片插入页面
15、静态资源打包之file-loader参数配置:
use: {
loader: ‘url-loader’,
options: {
name: ‘[name]_[hash].[ext]’, // 使用老静态资源的名字和后缀名。[xxx]称之为placeholder,占位符.
outputPath: ‘images/’ // 存放于dist目录(webpack配置中output.path配置整个工程dist目录)
}
}复制代码
16、url-loader: 和file-loader非常类似,只不过多了一个limit的配置项。url-loader完全实现了file-loader能做的,不同之处会根据limit阈值把图片转为base64字符串,打包bundle.js里面。
好处是省了一次HTTP请求,问题是图片很大时,bundle.js很大,页面加载很久才能出来。
url-loader参数配置(和file-loader完全一样,只是多了个limit配置项):
options: {
name: ‘[name]_[hash].[ext]’,
outputPath: ‘images/’
limit: 2048 // 单位字节,小于2kb的才打包进入bundle.js
}复制代码
17、静态资源打包之(css样式篇)
use: {
loader: [‘style-loader’, ‘css-loader’] // 因为需要2个loader,就不能写成对象值形式,要用数组形式。
}
head里面的style标签,是style-loader帮我们挂载上去的,所以在css-loader处理css文件时需要配合style-loader使用。
18、使用先进css语法之sass-loader: 需安装sass-loader node-sass
use: {
loader: [‘style-loader’, ‘css-loader’, ’sass-loader’] // loader是从右到左的执行顺序
}
19、产商前缀自动添加之postcss-loader: 给CSS3新属性自动添加 moz- webkit- 等厂商前缀
需新建postcss.config.js配置文件:
plugins: [require(‘autoprefixer’)] // 配置需要使用的插件,当postcss-loader被使用的时候,会去使用一
// 个叫autoprefixer的插件,帮我们去加上厂商前缀
webpack配置:
use: {
loader: [‘style-loader’, {
loader: ’css-loader’, // 如果需要多个loader,还要给loader传参数,就不能写字符串了,要写对象
options: {
importLoaders: 2, // 通过import方式引入的资源也要去走2个loader,sass-loaderpostcss-loader
modules: true // css module,模块化css让JS模块引入css只在当前模块有效,对其他引入模块无效
}
}, ’sass-loader’,’postcss-loader’]
}
复制代码
20、字体文件(www.iconfont.cn 下载iconfont.css)静态资源,需要file-loader打包拷贝至目标目录。下载字体并拷贝样式至项目详细,参考lesson 3-4。
webpack官方文档介绍:DOCUMENTSTION目录下的Asset Management子目录,介绍了所有的资源文件打包,以及数据文件(如.csv格式的Excel表格)打包的解决方案。
21、使用plugins让打包更便捷:
1、安装html-webpack-plugin,然后require引入,再webpack配置:
(这个插件会在打包结束后自动生成一个HTML文件,并把打包生成的JS自动引入到这个HTML文件中。)
2、安装clean-webpack-plugin, 然后require引入,再webpack配置:
(这个插件会在打包前自动清除指定目录)
plugins: [
new HtmlWebpackPlugin({ template: ‘src/index.html’ }), // 支持非常多的配置参数,详细看官网
new CleanWebpackPlugin([‘dist’]) // 也可以用linux命令放至脚本中实现
]复制代码
22、Entry和Output基本配置
entry写字符串,等于写键值对(默认的键名是main),如果output.filename指定,覆盖main作为文件名。
output:output.publicPath: ‘cdn.com.cn’ // 所有页面引入的JS都会加上这个地址。当我们的项目,后台服务器用index.html,而静态资源放到CDN的情况下,就会用到output.publicPath配置项。
23、SourceMap配置: sourceMap是一个映射关系,它知道dist目录下main.js文件中的96行实际上对应的是src目录下index.js文件中的第一行。方便我们去源代码排错,而不是去目标代码(打包出来的)排错。
mode: ‘development’, // development模式下自动配置了sourceMap
devtool: ‘none’ 或 ‘eval’ 或 …… // none表示关闭sourceMap.
eval: 以eval方式执行代码,map映射放在js里。虽包速度最快,不生成.map文件,但不适于复杂代码。
cheap-source-map : cheap的意思是只标记出错行,不标记列。只管业务代码的出错,不管第三方插件或loader的出错。
cheap-module-source-map: module的意思是既管业务代码的出错,还管第三方模块,loader出错等。
inline-source-map: inline的意思是把.map文件合并到打包生成的目标js里,以base64编码放在末尾。
最佳实践:
开发环境:cheap-module-eval-source-map。提示较全,打包较快。
线上环境:cheap-module-source-map。
24、使用WebpackDevServer提升开发效率:源码改变,实现自动重新打包。
方式一:命令webpack —watch // watch参数会监听文件改变,自动打包,但需手动刷新浏览器
方式二:安装webpack-dev-server,配置参数:
devServer: { // webpackDevServer会帮助我们启动HTTP一个服务器
contentBase: ‘./dist’, // 服务器的根目录,就是当前的打包输出目录
open: true // 自动打开浏览器,并访问localhost:8080
port: 8080,
overlay: true,
proxy: {
‘/api’: ‘http://localhost:3000' // 访问localhsot:8080/api 地址会直接转发到 localhost:3000
}
}复制代码
命令:webpack-dev-server // 会监听文件改变,自动打包,同时自动刷新浏览器
vue 和 react的底层都使用了webpack-dev-server,所以在vue-cli和react-cli的官方脚手架都同样配置了。
方式三:自己写服务器监听文件改变,自动打包。
1、安装webpack-dev-middleware(做监听) express(做HTTP服务器)
2、const express = require(‘express’) webpackDevMiddleware= … webpack= … config= …
3、const compiler = webpack(config) // 返回一个编译器,编译器执行一次,重新打包一次代码
4、const app = express() app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}))
app.listen(8080, () => { console.log(‘server is running’) })
复制代码
25、webpack Hot Module Replacement 热模块更新
div:nth-of-type(odd) { background-color: blue; } // 偶数的子元素背景为蓝色
webpac-dev-server帮我们实现自动打包和自动刷新浏览器,但如果我们只改变了某个模块的代码,也会浏览器重新请求页面导致整个页面刷新,这样之前做的交互就会丢失,交互又得重做一遍。 配置参数:
plugins: [new webpack.HotModuleReplacementPlugin()] // 通过HMR插件实现HMR
改动的模块重新请求下来后,不会自动执行,需要在引入模块的代码中加入下代码,把更新的模块重新执行一遍。css模块不用自己写这样的代码,是因为css-loader内置了这样的代码。原则上所有想实现热模块更新类型的文件都需要写这样的代码,不需要写的是已经内置了。
if(module.hot) {
module.hot.accpet('./number.js', () =>
number();
})
} 复制代码
当你在代码中去引入其他模块的时候,如果希望某个模块代码发生变化,只希望更新这个模块,就要用到HMR技术。
26、使用babel处理ES6语法(参考Babel官网)
低版本IE浏览器和国产浏览器没有像Chrome与时俱进,更新慢。
ES6转ES5:可以 let 转 var () => {} 转普通函数 function() {}
不可以转:Promise变量和map语法等 需要借助babel-polyfill,在所有代码运行前补充到低版本浏览器里
import “@babel/polyfill” // 在main.js文件头引入模块,这样代码会内联展开并早于业务代码执行
需要安装: babel-loader @babel/core babel核心库,识别JS代码,把JS代码转换为AST抽象语法树,再将AST树编译转化为新的语法。 babel-preset-env包含了所有ES6转ES5的规则。
rules:[{
test: /.js$/,
exclude: /node_modules/, // node_modules目录里面是第三方代码,第三方模块自己做过ES6转ES5了
loader: ”babel-loader”,
options: {
presets: [“@babel/preset-env”] // 不传参写法,下面传参写法
presets: [[“@babel/preset-env”, { useBuiltIns: ‘usage’ ,
targets: {
chrome: ‘67’, // 项目会打包运行在大于67版本的浏览器下,让babel根据情况是否转换和填充
firefox: ’60’, edge:’17’, safari: ’11.1’….
}
}]]
}
}]复制代码
useBuiltIns:usage 当用babel-polyfill做填充的时候,根据业务代码用到什么才加什么。减少打包体积。
(webpack4中配置了useBuitin:’usage’的话,webpack会自帮我们引入babel-polyfill,无需手动引。)
通过以上配置,在我们代码中写任何ES6的代码都不会有任何问题了。但是在开发类库、第三方模块、组件库的时候,用babel-polyfille的方案有问题,因为在注入Promise、map等方法时会全局注入,污染全局环境,这时需要换一种配置方式,插件的方式。
(1)安装@babel/plugin-transform-runtime和@babel-runtime,不引入babel-polyfill避免污染全局环境,
(2)更改babel-loader的options配置:(建议是babel配置项非常多,单独建.babelrc配置文件更合适)
options: {
“plugins”: [[“@babel/plugin-transform-runtime”, {
“corejs”: 2, // 默认false,改成2需要额外安装@babel/runtime-corejs2代替@babel-runtime
“helpers”: true,
“regenerator”: true,
“useESModules”: false
}]]
}复制代码
27、配置React代码的打包 (待看)
28、Tree Shaking
当index.js引入a.js(a.js中有多个方法)中的某一个方法,webpack默认会把a.js中所有方法打包进来。增加了chunk体积。webpack2.0以后提供了摇树功能,但Tree Shaking只支持ES Module(import方式引入的ES Module底层是静态引入,require 引入的Common JS模块是动态引入。)
摇树配置:development模式需要手动配,配好了代码体积也不变少 production模式下默认配好了
(1) webpack.ptimization: {
usedExports: true,
}
(2) package.json中配置:”sideEffects”: false, // 避免 import “babel-polyfill”形式引入全部代码被忽略
或指定具体免忽略清单: “sideEffects”: [‘@babel/poly-fill’, ‘*.css’] // *.css避免忽略任何css复制代码
29、development和production模式的区分打包:production模式下不需要DevServer和new webpack.HotModuleReplacementPlugin(),代码会压缩,sourceMap配置不一样。
配置抽取:webpack.common.js webpack.dev.js webpack.prd.js 通过安装webpack-merge插件合并。
30、Webpack和Code Spliting
plugins: [
new CleanWebpackPlugin([‘dist’], {
root: path.resolve(__dirname, ‘../‘) // 指定根目录,该插件默认把当前目录作为根目录
})
]复制代码
安装lodash 工具集合包 import _ from ‘lodash’
默认Webpack 会把工具包和业务代码打包到一个文件,打包文件会很大,加载时间会长,且业务代码一变更打包结果跟着变更,实际上lodash第三方工具包并没有变更。
解决办法(Code Spliting):
第一种:自己分割 (代码分割和webpack无关,在webpack前我们手动分割代码到不同JS文件,然后根据依赖关系顺序引入JS文件)
将第三方工具包拆出来,新建common.js文件,在common.js文件引入各个第三方工具包,并挂在到window全局对象上,然后在页面的entry入口新增一个:
entry: {
common: ’./src/common.js’,
main: ‘./src/index.js’
}复制代码
这样打包出来后,index.html会先后引入common.js和main.js,执行结果和将第三方工具包和业务代码打包到一个文件一样。
第二种:webpack中实现代码分割,两种方式
(1)同步代码:只需在webpack中做optimization的配置,借助Webpack插件SplitChunkPlugin实现
optimization: {
splitChunks: {
chunks: ‘all’ // 同步代码需要这样配置才会代码分割,异步代码webpack会自动代码分割到单独文件
}
}复制代码
遇到如下代码,webpack会去分析代码,把该提取出来的文件提取出来单独存放:
import xxx from ‘xxx’
// 接着是写业务代码
(2)异步代码:具体通过JSONP异步加载文件,只要是异步加载,webpack会自动做代码分割单独存放到一个文件里去。如下
function getComponent() {
return import(/* webpackChunkName: “wqloadsh”*/‘lodash’).then(( default: _ )) => {
return _.join([‘a’,’b’,’c’])
} // /* webpackChunkName: “wqloadsh”*/ 魔法注释,异步模块打包出来命名成verndors~wqloadsh.js
} // 若不做上述注释,则loadsh异步模块会被打包出来命名成0.js。复制代码
@babel/plugin-syntax-dynamic-import babel官方提供的这个插件支持魔法注释
31、SplitChunksPlugin配置参数详解
optimazation配置全部是设置同步代码分割和分组规则的。因为异步代码分割就是单独打包到一个文件。
optimazation: {
chunks: ‘async’, // 默认async表示只对异步代码做代码分割。all表示对同步、异步都做代码分割。
minSize: 3000, // 引入的同步模块大于3kb才做代码分割
maxSize: 5000, // 当vendor.js大于5Kb的时候做二次分割,基本不使用这个。
minChunks: 2, // 当一个同步模块被多少个打包出来的chunk引用的时候才做代码分割
maxAsyncRequests: 5, // 同时加载的模块数最多是5个。
maxInitialRequests: 3, // 入口文件main.js做代码分割不超过3个。
automaticNameDelimiter: ‘~’, // 组和文件之间的连接符号
cacheGroups: { // 缓存组(即代码分割分组)规则。
vendors:{ // vendors组
test: /[/]node_modules[/]/ , // 代码分割时,在node_modules目录下的命名为vendors组
priority: -10 // 对同步异步代码都生效,因此上面异步模块打成单独文件时命名verndors~wqloadsh.js
filename: ‘vendors.js’ // 一旦发现同步引入的代码从node_modules目录的,都打包到vendors.js
} ,
default: { // default组,所有的模块都符合default组,都可以放到default组里。
priority: -20, // 但是default组的priority会设置的比vendors组低,所有模块优先匹配vendors组。
reuseExistingChunk: true, // 如果模块以及打包过,直接复用不重复打包。
filename: ‘common.js’ // 非node_module目录下的同步代码分割到common.js组。
}
}
}复制代码
32、Lazy Loading懒加载,Chunk是什么?
懒加载:通过import()异步加载函数来异步的去加载一个模块,初始不加载,执行import()函数时加载。懒加载不是webpack的概念,而是ES import()函数里面的概念,import().then() 说明import()函数返回的是Promise,如果浏览器不支持Promise则需要安装babel-polyfill。使用:
document.addEventListener(‘click’, () => {
import(/* webpackChunkName: ‘wqlodash’ */ ‘lodash’).then( { default: _ } => { _.join([‘aa’, ‘bb’]) })
}) // 异步模块vendors~wqloadsh.js初始不会加载,当点击页面任何位置时才回去加载
// Webpack的异步加载是基于Ajax的JSONP实现的,相比手动创建script标签后插入页面加载JS效果相同。复制代码
ES7 中async关键字写法:
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName: ‘wqlodash’*/ ’loadsh’)
_.join([‘aa’, ‘bb’])
}复制代码
chunk是什么:所有JS打包出来的(JS、css)文件都是一个chunk。
optimazation.minChunks: 2, // 当一个同步模块被2个打包出来的chunk引用的时候才做代码分割
33、打包分析,Preloading, Prefetching
官方打包分析:
(1) webpack — profile — json > stats.json 把整个打包过程的描述输出到stats.json文件
(2) webpack.github.io.analyse网站:把stats.json文件上传网站实现可视化分析
assets: 打包出来的静态资源
官方推荐可视化打包工具: webpack-bundle-analyzer
Preloading,Prefetching:
为什么optimization.chunks: ‘async’ 的默认值是async,不对同步代码做分割。这是因为同步代码分割只是把一次引入的main.js代码分割成vendors.js common.js main.js多个文件同时引入,利用缓存避免重复加载vendors.js,common.js这些公共重复代码,达到提高了第二次加载页面的访问速度效果。然而缓存带来的性能提升是非常有限的,真正对页面性能做优化,是需要第一次加载页面时加载速度就是最快的,靠同步代码分割(分割出vendors.js, common.js分组)是满足不了需求的。webpack真正希望我们编写代码的方式是怎样的:
首先观察页面性能:cmd + shift + p,搜索打开Coverage面板,点击开始录制屏幕。然后刷新页面,看到
加载的main.js文件利用率只有74%(红色标注表示未执行代码,绿色标注表示已执行代码)。
性能优化:写高性能代码的时候,现在重点考虑的不是缓存这样的东西,而是代码的使用率。把页面初始不
需要加载的代码变成异步代码,首屏时间就会更短,网页访问速度就会更快。webpack希望我们多去
写异步加载代码,才能让网站加载性能真正得到提升,这也是为什么optimization.chunks: ‘async’ 的
默认值是async,而不是all。
实际应用案例:比如登录弹框,在点击登录按钮后才弹出来,就可以把弹框代码放到点击按钮当中去
import()异步加载。也许会有疑问,如果点击按钮时才去加载异步代码,交互不就会很慢吗,那么
preloading、prefetch就是用来解决这个问题的。
preloading、prefetch:在网络带宽空闲的时候,偷偷的去下载异步代码。等真正执行import()去异步加载
时,其实已经下载到浏览器缓存,这时候就是利用浏览器缓存加载。
案例代码:
document.addEventListener(‘click’, () => {
import(/* webpackPrefetch: true */ ’./click.js’).then(({ default: func }) => {
func() // /* webpackPrefetch: true */ 魔法注释,webpack能识别,实现异步代码prefetch
})
}) // 把首屏不会执行的交互代码放到了click.js文件,在使用时再异步加载。复制代码
prefetch、preload区别:prefetch等主流程加载完,有空闲再加载,更合适。 preload和主业务文件一起
加载。prefetch在某些浏览器上会有兼容问题。
总结:异步代码+preload/prefetch 是最佳方案。既能实现首屏性能最优,又能实现交互性能最优。
34、CSS文件的代码分割
webpack打包默认css直接打包到JS里(css in js):
output: {
filename: ’[name].js’, // entry中入口文件JS打包名称走filename配置项,直接被页面引入
chunkFilename: ‘[name].chunk.js’, // 间接引用(main.js引入)的JS文件打包名称走chunkFilename配置
}复制代码
使用插件mini-css-extract-plugin(底层依赖SplitChunkPlugin)可以抽取CSS到单独文件,使用:
(1)loaders配置:
test: /.css$/, use: [
{ loader: MiniCssExtractPlugin.loader, options: { publicPath: ‘../‘}}, //最后一步不能再用css-loader
‘css-loader’
]复制代码
(2)plugins配置:
plugins: [
new MiniCssExtractPlugin({
filename: ‘[name].css’, // 被页面直接引用的css名称走这个配置,这个插件默认合并同步css模块。
chunkFilename: ‘[name].chunk.css // 间接引用(main.css引入)的css名称走这个配置
}),
new OptimizeCssAssetsWebpackPlugin({}) // 压缩css的插件
]
optimization.splitChunks: {
cacheGroups: {
style: {
name: ‘styles’,
test: ‘/.css$/‘,
chunks: ‘all’, // 将所有的css模块,包括同步代码、异步代码,都打包到styles.css一个文件。
enforce: true // 忽略minSize、minChunks等所有配置参数
},
fooStyles: { // MiniCssExtractPlugin官方代码:介绍拆分不同入口JS中引用的CSS代码到不同CSS文件
name: ‘foo’,
test: (m,c,entry = ‘foo’) => … // entry中foo.js入口文件中引用的CSS单独打包到foo.css
},
barStyles: {
name: ‘bar’,
test: (m,c,entry = ‘bar’) => … // entry中bar.js入口文件中引用的CSS单独打包到bar.css
},
}
}复制代码
注意:如果在optimization.splitChunks配置了usedExports: true实现treeShaking,则必须在package.json中添加”sideEffects”: [“*.css”]使CSS不被摇树。因为import “xx.css”只引入未显示使用会被摇掉。
35、webpack与浏览器缓存(Cacheing)
performance:false // 关闭webpack性能警告提示
解决浏览器缓存问题: 浏览器非强制刷新不会重新拉取新打包出的同名资源,使用contenthash解决。
output: {
filename: ‘[name].[contenthash].js’,
chunkFilename: [name].[contenthash].js
}
老版本的webpack4存在问题,代码不变时,打包出来的contenthash会变,解决:
optiomization: {
runtimeChunk: { name: ‘runtime’ } // 把manifest关系代码提取到runtime.js
}复制代码
原因:业务逻辑代码(main.js)和第三方库(vendors.js)是有关联的,webpack把这关联部分称之为manifest。manifest代码既存在于main.js,也存在于vendors.js,这样即使未改变代码,manifest每次打包都会有差异,同时main.js和vendors.js中的内容也跟着变了,contenthash也就变了。在旧版webpack4中需要我们手动配置抽取manifest代码,新版webpack4已默认帮我们处理。
36、Shimming(加垫片:形式包括babel-polyfill, ProvidePlugin, imports-loader, Authoring Lib等)
webpack打包是基于模块的,模块与模块之间的变量是隔开的。比如a.js引入了jQuery,那么b.js中要想使用$符号,必须自己引入jQuery。
Shimming可以理解为AOP(如过滤器),全局的更改webpack对模块的默认打包行为。
(1) ProvidePlugin:
plugins: [
new webpack.ProvidePlugin({ // 此插件可自动帮所有模块引入使用了的库
$: ‘jquery’, // 当webpack发现模块里面用了$符号,webpack会在模块里面自动引入jquery
_: ‘lodash // 帮使用了_符号的模块自动引入lodash
_join: [‘lodash’, ‘join’], // 只引入lodash模块中的join方法
})
]复制代码
(2) imports-loader: 每个模块的this默认指向模块自身,imports-loader可以改变所有模块的this,如全部指向window:
{
test: /.js$/,
use: [ { loader: ‘babel-loader’ },
{ loader: ‘imports-loader?this=>window’ } // 更改webpack默认行为
]
}复制代码
37、环境变量的使用
webpack —env.production —config ./build/webpack.common.js //向配置文件传了env.production=true
webpack.common.js:
module.exports = (env) => {
if(env && env.production) return merge(commonConfig, prodConfig)
else return merge(commonConfig, devConfig)
}复制代码
38、Library库的打包--打包一个库
1、math.js库
export function add() ... mup
2、string.js库
export function join() ... split
3、index.js
import * as math from ‘math.js'
import * as str from 'string.js'
export default { math, string }
这样index.js暴露出了工具给别人使用,但是这样直接在浏览器上不能运行,需要先进行webpack打包。
webpack配置:
module.exports = {
mode: 'production',
entry: './src/index.js',
externals: ["lodash"], // 声明依赖的外部库,不打包到库里面,而是让业务代码去加载lodash,避免重复打包
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
libraryTarget: 'umd', // 支持任何模块形式的引入,如import,const xx =require,require (['library'], function() {}) ,
library: 'library', // 支持 形式引入,直接挂载library对象到 全局对象window上
}
}
其次,library和libraryTarget可以配合使用,libraryTarget: 'window'指定以标签引入是挂在到window对象上,如果在node中则可以设置libraryTarget: 'global'挂在到全局global对象上。
最后,修改package.json中 "main": "./dist/library.js" // 表示最终暴露给用户使用的文件
npm publish 上面工程库,用户npm install就可以安装使用。 复制代码
39、PWA的打包配置:Web技术中的一种
实现第一次访问成功,第二次网站挂了,这时可以通过PWA缓存访问。
npm i workbox-webpack-plugin
const WorkboxPlugin = require("workbox-webpack-plugin")
new WorkBoxPlugin.generateSW({ // 只需在生产环境中使用
clientClaim: true,
skipWaiting: true
})
这样打包之后dist目录会多出service-worker.js(可以理解为另类缓存)、precache-manifest.js两个文件。
还需手动加入以下代码:
if('serviceWorker' in navigator) { // 如果浏览器支持ServiceWorker
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then().catch()
}
}
复制代码
测试:先启动服务器,通过浏览器访问localhost:8080可以访问。然后关闭HTTP服务器,再次访问localhost:8080还是可以访问。
40、TypeScript的打包配置
TypeScript是前端代码约束解决方案,主要增强代码可维护性。
TypeScript主要作用是支持对属性和参数的类型校验,可以有效避免调用函数时传参错误。 如果是一个团队协同编程,每个人代码风格不同,同一个功能写法迥然不同,维护性难以得到保证。 规范了TypeScript的语法,也支持原始的语法,通过TypeScript最大的优势是规范我们的代码,可以有效提高TypeScript编码的可维护性。
TypeScript的webpack配置:
1、npm i ts-loader typescript --save-dev
{
test: /.tsx?$/
use: 'ts-loader',
exclude: /node_modules/
}
2、根目录新建TypeScript配置文件tsconfig.json:
{
"compilerOptions": {
"outDir": "./dist", //可不配, webpack output中已配置
"module: "es6",
"target": "es5", // TypeScript语法打包最终输出形式
"allowJs": true
}
}
我们如果引入了外部的库,不能直接利用TypeScript自动检测的特性,如果想要Typescript也能支持
这些库错误提示,就需要安装相应库的类型文件。
npm i @types/lodash --save-dev // 安装lodash的类型文件 这样支持识别loadsh里面的传参要求
npm i @types/jqueery --save-dev // 安装jquery的类型文件 这样支持识别jquery函数的传参要求 复制代码
41、WebpackDevServer实现请求转发
用途一:在开发环境实现跨域发送请求,底层使用http-proxy-middleware
用途二:开发环境后端域名和生产环境域名不同。
例如开发环境中,8080端口启动应用,
则axios.get('/react/api/header.json') 默认请求localhost:8080/react/api/header.json
实际要请求的是http://www.dell-lee.com/react/api/header.json,需要配置devServer:
devServer: {
proxy: {
'/react/api': 'http://www.dell-lee.com'
// 把请求/react/api路径下的所有请求转发到http://www.dell-lee.com下,
// 再把拿到的内容以localhost:8080/react/api/header.json返回给ajax。
'/react/api': { // 更高级的配置
target: 'http://www.dell-lee.com',
pathRewrite: {
'header.json': 'demo.json', // 如果请求header.json的数据,实际去拿demo.json的数据
},
secure: false,// 对HTTPS生效
changeOrigin: true, // 改变请求origin选项,突破后端对origin限制,例如反爬虫
}
}
}
线上代码: 不会走devServer的配置,默认请求前端所部属的服务器地址,适用于前后端部署在同一个tomcat
容器服务器里。如果前后端是分开部署的,则需要配置axios的baseUrl指向后端服务器地址。
复制代码
42、WebpackDevServer解决单页面应用路由问题
React单页引用中这种写法:
如果直接访问localhost:8080/list.html内容会是空。
devServer: { historyApiFallback: true } // 开发环境中,访问非index.html页面时
更改为访问index.html页面,解决单页应用路由问题。
这只在开发环境中配置有效,如果是在线上环境,前端无能为力,需要后端nginx去做处理。
复制代码
43、Eslint在webpack中的配置
1、Eslint基础
约束代码,统一团队编程风格。
根目录运行 npx eslint --init // 快速初始化Eslint配置文件并安装相关依赖
运行npx eslint src // 检测src目录下的代码规范
.eslintrc配置文件:
{
"extends" : "airbnb", // 变态airbnb规则, 还可选Google...
"parser": "babel-eslint", // 较常用,需安装 babel-eslint
"rules" : {
"react/prefer...": 0, // 表示不遵循这个要求
},
globals: {
document: false, // 代码中不允许对全局变量document进行覆盖
}
}
2、webpack中配置Eslint
npm i eslint-loader --save-dev // 通过esling-loader把Eslint和webpack结合起来
{
test: /.js$/,
use: ['babel-loader', { loader:'eslint-loader', options: { fix: true } }],
// loader从后往前执行,打包前检测代码规则,但这样会降低打包速度。
// git 钩子中执行 eslint src可以提高打包速度,但是就失去了交互式提示的便捷。
}
devServer: {
overlay: true, // 浏览器上弹层显示错误信息
}
复制代码
44、Webpack性能优化--提升webpack打包速度的方法
1、跟上技术的迭代(Node,npm, yarn) webpack版本升级会提升速度,webpack依赖node,升级node 会提升速度,npm升级会提升包的分析和依赖管理性能。
2、在尽可能少的模块上应用loader: 比如使用include,exclude配置,减少loader处理的模块数。
rules:[{
test: /.js$/,
include: path.resolve(__dirname, '../src'),
exclude: /node_modules/,
use:[{ loader: 'babel-loader'}]
}]
复制代码
3、Plugin尽可能精简并确保可靠 -- 尽可能少的使用Plugin,选择官方推荐,社区认可性能好的插 件。 optimization: { mnimizer: [new OptimizeCssPlugin({})] // 压缩CSS插件,生产打包才使用,开发环境不使用 }
4、resolve参数合理配置
module.exports = {
resolve: { //
extensions: ['.js', '.jsx'] // 当去引入一个路径下的模块时,先查找该路径下
对应的以.js结尾的文件,没找到则找以.jsx结尾的文件
//extensions 一般只配置.js逻辑性文件 不建议把.css .jpg文件等配置在里面,减少文件搜索
mainFiles: ['index', 'child'] // 引入路径时默认引入路径下的index.js或child.js
alias: {
child: path.resolve(__dirname, '../src/child') //别名child是src/child.js文件路径
@: path.resolve(__dirname, '/src') // 别名@是src根目录
}
}
}复制代码
5、使用DllPugin提高打包速度
目标:第三方模块只打包一次。因为第三方模块代码是不会变的,webpack每次打包的时候都会去分 析并打包。实现第三方模块第一次打包的时候去分析、查找和打包,后面不再重复分析、查找和打包。
第一步:新建webpack.dll.js: 把react,react-dom,loadsh第三方模块打包到一个dll文件中。
mode: 'production',
entry: {
vendors01: ['react', 'react-dom', 'lodash']
},
output: {
filename: [name].dll.js,
path: path.resolve(__dirname, '../dll'),
library: '[name]' // 将打包出的文件暴露为一个全局变量vendor01供引入使用
}
plugins: [new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manefist.json) // 给打包出的第三方模块dll
包生成映射文件
})]
执行webpack --config webpack.dll.js 输出:
1、vendors01.dll.js合成了多个第三方库,通过vendors01全局变量暴露。
2、vendors01.manifest.json映射文件保存了这些第三方库和vendors01的映射关系。
第二步:在项目webpack.common.js中
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/vendors01.dll.js')
})
将第三方库模块dll包引入入口页HTML,引入后控制台可以访问到vendors01全局变量。
webpack.DllReferenecePlugin({
manifest: path.resolve(__dirname, '../dll/vendors01.manefest.json')
})
将第三方模块包dll的映射文件引入,当代码引入第三方模块时,先通过映射文件中的映射关系从
全局变量vendors01中去取,没找到才去node_modules目录查找。
在大型项目中,DllPlugin和DllReferencePlugin用的还是比较多的。还会经常将第三方库分组:
entry: {
vendors01: ['lodash'],
react: ['react', 'react-dom'],
jquery: ['jquery']
},
这样在webpack.common.js中引入使用时,要为每个分组都要需要创建一AddAssetHtmlWebpackPlugin
和DllReferenecePlugin。可以写个函数,使用fs去遍历dll目录下的文件名,通过循环添加。 复制代码
6、控制包文件大小
做项目打包的时候,应该让打包生成的文件大小尽可能小。有的时候经常在代码中引入一些没有使用 的模块,引入后如果没有做treeshaking,就会在打包出的代码中多出很多冗余的代码,这些代码会拖 累webpack的打包速度。所以源代码中没有用到的包不要引入,或通过treeshaking处理。这样可以控制 webpack打包大小,提高打包速度。
7、thread-loader, parallel-webpack, happypack多进程打包
webpack默认使用node单进程打包,可以使用上述工具多进程打包,具体参考官方文档API。
8、合理使用sourceMap: sourceMap越详细,webpack打包越慢。
9、结合stats分析打包结果: 输出打包统计数据文件,再上传到线上网站分析。
10、开发环境内存编译
如在开发环境中,webpack devserver把dist输出放在内存中,读取 更快。
11、开发环境无用插件剔除
开发环境设置模式mode为development,不压缩文件,不使用线上环境才需要的插件和配置。
45、多页面打包配置
entry: { // 多个入口文件
aa: './src/a.js',
bb: './src/b.js'
}
plugins:[
new HtmlWebpakPlugin({ // 多个入口页面
template: 'src/index.html',
filename: 'aa.html',
chunks: ['runtime', 'venodors', 'aa.js'] // 指定入口页需要引入的chunks
})
new HtmlWebpakPlugin({ // 多个入口页面
template: 'src/index.html',
filename: 'bb.html',
chunks: ['runtime', 'venodors', 'bb.js'] // 指定入口页需要引入的chunks
})
]
输出:
aa.html引入runtime.js, vendors.js, aa.js
bb.html引入runtime.js, vendors.js, bb.js
页面很多是,写个函数使用fs遍历src目录下的入口js文件,再填充entry,plugins的多页面配置。
复制代码