webpack的学习
虽然以前接触过webpack,但是一开始webpack学习后考虑到当时很菜的我并没有什么运用的地方
因此一开始就学习的很草率,什么也没有记住,现在重新再学一次
想要使用webpacj,首先得安装webpack和webpackcli
但是不建议安装在全局可以在某个文件夹下单独npm init 建立package.json文件以后再npm安装在该目录下
npm install webpack -S
npm install webpack-cli -S
在src文件下需要新建一个index.js作为js文件的入口
被外部的index.html引入
npm run build && npx webpack
会在目录下生成一个dist文件夹,内部包含一个main.js 包含打包后的代码
webpack内置指令很多,可以通过npx webpack –help查看
自定义文件的打包路径和打包文件名 path需要引入path模块拼接形成绝对路径,否则会报错
因为打包完后的路径是dist/main.js,因此需要修改index.html的js文件路径
插件
每次手动去index.html修改dist下的路径太麻烦,因此需要插件
想要使用插件需要require引入,然后放入plugins中
HtmlWebpackPlugin
npm install -S html-webpack-plugin
new实例化 在dist文件夹下单独生成一个引入了js文件的html
如何使用原来的index.html入口
npx webpack ! 冲啊!
清理dist
每次重复生成dist内部文件,需要在每次构建前清除dist文件 再重新生成,需要使用output.clean
搭建开发环境
设置mode = ‘development’
设置source map 因为webpack打包源码时很难追踪到错误和警告所处位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry:'./src/index.js', output:{ filename:'haoge.js', path:path.resolve(__dirname,'./dist'), clean:true }, mode:'development', devtool:'inline-source-map', //在开发模式下追踪代码 plugins:[ new HtmlWebpackPlugin({ template:'./index.html', //打包生成的文件模板 filename:'app.html', //打包生成的文件名称 默认是index.html // 资源文件注入模板的位置 inject:'body' }) ] }
|
由于每次都需要重新打包启动浏览器观看 很麻烦 可以使用 watch mode观察模式
能够自动检测代码变化,不需要重新打包,但是再浏览器中想要查看最新的代码依旧需要刷新
因此需要使用webpack-dev-server
1 2 3 4 5 6 7
| npm install webpack-dev-server -S module.exports = { devServer:{ static:'./dist' //将dist下的文件设置为web服务的目录 }, } npx webpack serve --open
|
资源模块
webpack内置模块asset modules引入其他类型文件
1 2 3 4
| asset/resource 发送一个单独的文件并导出URL asset/inline 导出一个资源的data URI asset/source 导出资源源代码 asset 在导出一个data URI 和发送一个单独的文件之间自动选择
|
引入资源匹配
修改src内容引入资源创建dom渲染
注:不能再npx webpack serve –open下打开 必须先npx webpack打包后 再npx webpack serve –open启动
自定义输出文件名
1 2 3
| output:{ assetModuleFilename:'image/[contenthash][ext][query]' }
|
会将文件导出带dist下的image目录 默认命名为image/[contenthash][ext][query]
也可将某些指定资源打包到特定目录
1 2 3 4 5 6 7 8
| rules: [{ test: /\.png/, type: 'asset/resource', // 优先级高于 assetModuleFilename generator: { filename: 'images/[contenthash][ext][query]' } }]
|
inline资源行内
1 2 3 4 5 6
| module:{ rules[{ test:/\.svg/, type:'asset/inline' }] }
|
会将文件作为URL注入dom的src
自定义webpack的URL生成器
webpack自带的inline编译的url是base64,如果想自定义url,可以下载一个自定义函数来编码文件内容
npm install mini-svg-data-uri -D
source资源
1 2 3 4 5 6 7 8 9 10
| mocule:{ rules:[{ test:/\.txt/, type:'asset/source' }] } // 遇到一个以前没接触过的写法 dom.style.cssText = `width:100px;height:200px;background:skyblue` // 定义样式 dom.textContent = 'cabbage' // //最后生成一个200px宽高,内容是文本cabbage的盒子
|
通用资源
设置type为asset以后,webpack会自动识别,小于8k被视为inline,反之视为resource
可以在webpack中设置限制的大小
1 2 3 4 5 6 7 8 9
| { test:/\.jpg/, type:'asset', parser:{ dataUrlCondition:{ maxSize: 4 * 1024 // 限制4kb } } }
|
Loader
l除开资源模块,可以使用loader引入其他类型文件
webpack只能识别js和json,loader能够让其他类型文件转换为有效模块,供应用程序使用
loader具有两个属性,test:匹配对应文件,use:定义应该使用哪种loader
加载CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| //为了import css文件,需要安装style-loader与css-loader npm install --save-d style-loader css-loader { test:/\.css$/i, use:['style-loader','css-loader'] //顺序固定,不然可能会抛出错误 } // 也可以支持less,sass等预处理器 // npm install less less-loader --save-dev // 匹配less文件 { test:/\.less$/i, use:['style-loader','css-loader','less-loader'] }
@color:yellow; .world { color: @color; }
|
抽离和压缩CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| npm install mini-css-extract-plugin --save-dev // 该插件会将包含css的js文件创建一个css文件,并支持按需加载和sourcemap,必须要webpack5才能运行 const miniCssExtractPlugin = require('mini-css-extract-plugin') plugins:[ new MiniCssExtractPlugin({ filename:'styles/[contenthash].css' }) ] { test:/\.css$/i, use:[miniCssExtractPlugin.loader,'css-loader'] }
npx webpack //将css文件打包在dist/styles/xxxx.css 打开文件后发现样式代码并没有压缩 需要安装css-minimizer-webpack-plugin npm install css-minimizer-webpack-plugin --sace-dev optimization:{ minimizer:[ new CssMinimizerPlugin() ] }, // 有一个地方不能忽视,需要将mode改为production,否则压缩会失败
|
加载images图像
我们可以借助资源模块将图片混入我们的系统,也可以使用css直接引用文件
1 2 3 4 5
| ...... backImg { background-image: url('') // style.css 我在玩的时候引入的背景图片大小是400多k,会直接遮罩警告,但是可以关闭,所以丝毫不慌 }
|
加载font
使用资源模块
1 2 3 4 5
| { test:/\.(woff|woff2|eot|ttf|otf)$/i, type:'asset/resource' } // 引入阿里巴巴字体图标库......
|
加载数据
webpack还可以加载xml,json,csv,tsv等文件,需要使用csv-loader和xml-loader
1 2 3 4 5 6 7 8 9 10 11
| npm install --save-dev csv-loader xml-loader
{ test:/\.(csv|tsv)$/i, use:['csv-loader'] },{ test:/\.xml$/i, use:['xml-loader'] }
// csv会被转化成数组 xml会被转化为对象
|
自定义JSON模块parser
通过自定义parser替代特定的webpack loader,可以将任何toml,yaml,或者json5作为JSON文件导入
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
| npm install toml yamljs json5 --save-dev 在webpack.config.js中配置 const toml = require('toml') const yaml = require('yamljs') const json5 = require('json5')
{ test: /\.toml$/i, type: 'json', parser: { parse: toml.parse, }, },{ test: /\.yaml$/i, type: 'json', parser: { parse: yaml.parse, }, },{ test: /\.json5$/i, type: 'json', parser: { parse: json5.parse, }, }
|
babel-loader
js文件需要编译吗?
webpack可以加载打包js文件,但是无法对js文件做出转换,会保持原样输出,可以使用babel-loader将es6转换成es5
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
| npm install -D babel-loader @babel/core @babel/preset-env babel-loader : 在webpack里应用 babel 解析ES6的桥梁 @babel/core : babel核心模块 @babel/preset-env : babel预设,一组 babel 插件的集 { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } // npx webpack // 按照文档来说应该会报regeneratorRuntime is not defined 但是我没报 挺神奇的 // 如果报错了那么就是babel未配置正确 # 这个包中包含了regeneratorRuntime,运行时需要 npm install --save @babel/runtime # 这个插件会在需要regeneratorRuntime的地方自动require导包,编译时需要 npm install --save-dev @babel/plugin-transform-runtime # 更多参考这里 https://babeljs.io/docs/en/babel-plugin-transform-runtime // 接着修改一下webpack.config.js的配置 { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: [ [ '@babel/plugin-transform-runtime' ] ] } } }
|
代码分离
代码分离是webpack的特性,可以将不同的代码分离到不同的bundle,可以影响加载时间
常用的代码分离方法:
1:入口起点,手动配置entry分离代码
2:防止重复,使用Entry dependencies 或者 SplitChunksPlugin 去重和分离chunk
3:动态导入,通过模块的内联函数调用来分离代码
1:入口起点
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 修改配置文件 entry:{ index:'./src/index.js', another:'./src/another-module.js' }, output:{ filename:'[name].bundle.js', path:path.resolve(__dirname,'./dist'), clean:true, assetModuleFilename:'images/[contenthash][ext][query]' }, // 在 another-bundle.js 和 index.js 中如果都引用了lodash 那么打包的体积就会增大 造成重复问题
|
2:防止重复
配置dependOn,可以在多个chunk共享模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 设置lodash为共享模块 会在dist下生成shared.bundle.js entry:{ index:{ import:'./src/index.js', dependOn:'shared' }, another:{ import:'./src/another-module.js', dependOn:'shared' }, shared: 'lodash', // index:'./src/index.js', // another:'./src/another-module.js' },
// 但是这种方式需要手动配置共享文件 可以利用SplitChunksPlugin插件将公共依赖模块提取到已有或者是新生成的chunk
|
3:动态导入
当涉及到动态拆分代码时,需要使用es的import或者webpack的遗留功能require.ensure
1 2 3
| // 1:import import './async-module' // 内部包含js代码 // 2:require.ensure
|
4:懒加载
在一开始不加载,在完成了某些操作之后再加载,优化响应速度
1 2 3 4 5 6 7 8 9
| const btn = document.createElement('button') btn.textContent = '点击执行加法' btn.addEventListener('click',()=>{ import(/* webpackChunkName: 'math' */ './math.js').then(({add})=>{ console.log(add(10,30)) }) }) // webpack 魔法注释: webpackChunkName: 'math' ,告诉webpack打包生成的文件名为 math // npx webpack打包完以后,在浏览器点击这个按钮才会引入并运行math.bundle.js
|
5:预获取/预加载模块
webpack4.6增加了对预获取和预加载的支持
使用prefetch和preload
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // prefetch 添加该魔法注释以后 浏览器回在闲置时间自动下载该math.js文件 btn.addEventListener('click',()=>{ import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({add})=>{ console.log(add(10,30)) }) }) // preload preload是当下时刻,prefetch是未来的时刻 preload立即下载,prefetch是闲置时下载 preload加载方式是并行的,prefetch是主chunk结束后执行 import(/* webpackChunkName: 'print', webpackPreload: true */ './print.js').then(({print})=>{ print() })
|
// prefetch 闲置加载
// preload 并行加载 是和index.bundle.js一起加载的
缓存
输出文件文件名
1 2 3 4 5 6
| // 可以通过修改替换outpur中的filename的substitutions设置,定义输出名称 // webpack提供了一个substitution(可替换模板字符串的方式) // 通过括号字符串来模板化文件名,[contenthash] ,substitution会根据资源内容创建出唯一hash,当资源内容变化时,[conetenthash]会自动发生变化 output:{ filename:'[name].[contenthash].js', },
|
缓存第三方库
在webpack中比较推荐将lodash等第三方库提取到一个单独的chunk中,利用client的长效缓存机制,减少不做修改的文件的请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 在optimization.splitChunks 添加如下 cacheGroups 参数并构建 optimization:{ // minimizer:[ // new CssMinimizerPlugin() // ], splitChunks:{ cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all', }, }, } },
|
将js文件放置到一个文件夹中
1 2 3
| output: { filename: 'scripts/[name].[contenthash].js', },
|
拆分开发环境和生产环境配置
目前只能手动调整mode切换开发环境和生产环境,考虑到很多配置在开发与生产环境不一样,因此需要区分开发和生产环境以便更灵活的打包
公共路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 在IE情况下// publicPath 可以用来指定应用程序中所有资源的基础路径 // 基于环境配置,如果在开发环境将assets目录下的文件夹托管至CDN,那么如何使用 // environment variable(环境变量) // webpack.config.js中 import { webpack } from 'webpack' const ASSET_PATH = process.env.ASSET_PATH || '/' output:{ filename:'scripts/[name].[contenthash].js', publicPath:ASSET_PATH }, plugins: [ // 这可以帮助我们在代码中安全地使用环境变量 new webpack.DefinePlugin({ 'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH), }), ], // Automatic publicPath webpack 会自动根据import.meta.url 、 document.currentScript 、 script.src 或者self.location 变量设置 publicPath。我们所需要做的是将 output.publicPath设为 'auto' // 在IE情况下,不支持document.currentScript,这时需要引入polyfill,例如currnetScript Polifill
|
环境变量
想要消除webpack.config.js在开发环境和生产环境之间的差异,需要环境变量environment variable
1 2 3 4 5 6 7 8 9 10
| // npx webpack --env goal=local --env production --progress // 可以通过该方式给webpack配置环境变量 // 通常module.exports 指向一个对象,想要使用env变量,那么就需要将module.exports转换成一个函数 module.exports = () => { return { ...... mode:env.production ? 'production' : 'development' ...... } }
|
拆分配置文件
目前不管是开发环境还是生产环境,使用的都是同一个配置文件,我们需要创建新的文件区分
webpack.config.dev.js
webpack.config.prod.js
1 2 3 4 5 6
| // ...文件忽略 在生产环境可以配置如下信息,隐藏webpack性能提示信息 performance: { hints:false }, npx webpack serve -c ./config/webpack.config.dev.js // 执行开发环境配置 npx webpack serve -c ./config/webpack.config.prod.js // 执行生产环境配置
|
npm脚本
1 2 3 4 5 6 7
| // 每次打包时都要npx xxxxxx // 在package.json中配置命令 "scripts": { "start": "webpack serve -c ./config/webpack.config.dev.js", "build": "webpack -c ./config/webpack.config.prod.js" } // npm run build 完美!
|
提取公共配置
在webpack.config.dev.js 和 webpack.config.prod.js 中存在大量相同配置webpack.config.common.js
1 2 3 4 5 6
| // 新建webpack.config.common.js 抽取dev和prod中相同的部分 ......此处省略一万字 //改写webpack.config.dev.js ......此处省略一万字 //改写webpack.config.prod.js ......此处省略一万字
|
合并配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // 在代码拆分完毕后 如何保证合并没有问题呢?利用webpack-merge // npm install webpack-merge // 创建webpack.config.js const { merge } = require('webpack-merge') const commonConfig = require('./webpack.config.common.js') const productionConfig = require('./webpack.config.prod.js') 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: throw new Error('No matching configuration was found!'); } }
|
内卷无出路,躺平才是真……