webpack相关文档
webpack了解
npm i webpack webpack-cli -D
npm i webpack-dev-server -D
假设执行webpack指令 webpack --config wb.config.js 是依赖webpack-cli来解析执行的
其实vue和react都有自己的cli,所以不需要使用webpack的cli
webpack --> node_modules/.bin下webpack --> 依赖webpack-cli runCli函数参数为webpack的config --> 依赖webpack的config打包
plugin
用于打包优化、资源管理、环境变量注入 例如:webpack提供的DefinePlugn插件
const { DefinePlugin } = require('webpack')
module.exports = {
entry: './src/index.js',
plugins: [
new DefinePlugin({ // 字符串内容会被当成代码来解析的
uname: "'sky'",
age: '18' // number 18
})
]
}
loader
例如:npm i ts-loader -D 必须要有一个配置文件tsconfig.json,可以通过tsc --init 指令创建,执行tsc指令需要全局安装ts,npm i typescript -g
- webpack只能理解js和json文件
- 使用loader可以让webpack去处理其他类型文件,
安装对应文件的loader插件在rules配置后转为有效(js)模块,以及被添加到依赖图中
module.exports = {
entry: "./src/index.js",
module: {
rules: [
{
test: /.css$/,
use: [ // 多个loader使用use 需额外配置为useEntry对象,不需要配置则字符串
style-loader
{
loader: 'css-loader',
options: {
// css文件内使用@import导入的css文件,会重新执行postcss-loader解析
// 如果不写这个属性则不会再次使用postcss-loader解析,1 = css-loader后面剩余loader的length
importLoaders: 1
}
},
// "postcss-loader" 使用postcss.config.js只写这个就行了
{
// postcss-preset-env 对css、less、scss 需要分别配置,所以可以选择创建一个postcss.config.js的文件
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env"
]
}
}
}
],
// loader: 'css-loader' 单个loader + 配置
},
],
}
};
postcss.config.js
module.exports = {
plugins: [
"postcss-preset-env"
],
};
自定义loader
这个文件可以参考看看自定义loader相关的
- Loader本质上是一个导出为函数的JavaScript模块,这个函数会接收三个参数
- content:资源文件的内容
- map:sourcemap相关的数据
- meta:一些元数据
loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去- 多个Loader使用,它的执行顺序是从后向前、从右向左的
- webpack在打包过程中会按照规则顺序调用处理某种文件的
loader,然后将上一个loader产生的结果或者资源文件传入进去,当前loader处理完成后再交给下一个loader - 还一个pitch-loader
loader的执行顺序是相反的:
- run-loader先优先执行PitchLoader,在执行PitchLoader时进行loaderIndex++
- run-loader之后会执行NormalLoader,在执行NormalLoader时进行loaderIndex--
如何改变它们的执行顺序:使用enforce
const schema = {
type: 'object', //options是一个对象
properties: {
//haha是一个number类型
haha: {
type: 'string'
},
}
}
module.exports = function (content, map, meta) {
// 获取关于loader的options参数, 校验loaderOptions参数 schema为一个json
const options = this.getOptions(schema);
console.log(options);
// 第二种同步 返回结果 (1)错误 (2)文本内容
// this.callback(null, content)
// 第一种同步 返回content
// return content
// 异步返回结果
const callback = this.async()
// 模拟网络请求
setTimeout(() => {
callback(null, content)
console.log('net done');
}, 1000)
}
module.exports.patch = function (content, map, meta) {
console.log('patch loader sky');
}
// 执行raw loader 处理图片、字体等文件 这时候content为Buffer类型的,而非字符串了
module.exports.raw = true
module.exports = {
output: {
path: resolve('build'),
},
resolveLoader: {
modules: ["./loader", "node_modules"]
},
module: {
rules: [
{
test: /\.js$/i,
use: [{
// "./loader/sky-loader" 传入的路径和context是有关系的
loader: "sky-loader.js",
options: {
haha: 1
}
}],
// 默认所有的loader都是normal,使用enforce改变loader的执行顺序
enforce: "pre"
}
]
}
}
mode (production 默认值 | development)
- 会影响
process.env.NODE_ENV值的变化 或 cli修改 webpack --mode=development - mode的值不同会影响webpack的默认配置
生产环境
- 需要压缩html/css/js代码/图片
- 可能需要分离CSS成单独的文件,以便多个页面共享一个CSS文件
开发环境
- 需要生产sourcemap
- 需要打印debug信息
- 需要live reload 和 hot reload的功能
devServer
npm i webpack-dev-server
- 会启动一个http开发服务器,把一个文件夹作为静态根目录
- 为了提高性能,使用的内存文件系统(
memory-fs) - 默认情况下devServer会读取打包后的路径(output的path配置路径),如果没有找到对应文件则会查找到devServer的static配置的路径
- 可以理解为静态文件根目录可以是有多个的
devServer的publicPath
作用:指定本地服务所在的文件夹,建议跟output的publicPath保持一致吧
output的publicPath属性
作用:指定index.html文件打包引用的一个基本路径
- 默认值是一个空字符串,所以打包后引入js文件时,路径是 bundle.js
- 开发中,可以将其设置为 / ,路径是 /bundle.js,那么浏览器会根据所在的域名+路径去请求对应的资源
- 打包文件插入到index.html里src编写方式:域名 + publicPath + filename =
‘http://127.0.0.1:5500/’ + '/’ + ‘js/sky.js‘
HMR(热更新)
- 默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可
- 在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading
原理: 没啥用,随便看看得了
- webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket)
- express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
module.exports = {
mode: 'development',
entry: './src/index.js',
devServer: {
hot: true,
compress: true, // 打包后文件gzip压缩,浏览器在解压 哈哈哈内存里哦
},
}
开启热更新,发生变化的代码模块才去更新,不会刷新整个页面,设置后会发现依然刷新整个页面,因为还需要手动设置
import "./pages/home"
console.log(111, uname, age);
const getName = () => 'sksksk';
getName()
console.log(getName());
console.log(module.hot, 'module');
// 了解了解得了,框架帮忙做hmr咯,例如:vue-loader、react-refresh
if(module.hot) {
module.hot.accept('./pages/home', () => {
console.log('home更新了');
})
}
devtool 决定打包文件的错误提示信息
可选参数
- cheap:报错只有行信息,不包含列
- inline:生成xxx.map.js文件,也会生成source-map文件但是以DataUrl的形式添加到bundle(打包)文件的后面
- eval:默认不生成sourcemap,在eval内执行代码,如果有sourcemap则将sourcemap以字符串的形式添加到eval函数中
- hidden:生成sourcemap文件,但不会对其引用
- nosources:也会生成sourcemap文件,但是报错点进去没提示,不会生成源代码文件
source-map:生成xxx.map.js文件
Babel 之 我是一个编译器!嘿嘿
npm i babel-loader @bable/cli @babel/core @babel/preset-env -D
作用:语法转换,源代码转换等
抽离:创建babel.congig.js文件单独配置预设,可以不在webpack.config.js的loader中配置了
webpack不会转换Import 和 export 语句以外的ES6代码,如果需要转义其他ES6代码还需要使用babel
babel-loader:作用是调用babel-core@babel/core:本身只提供一个过程管理的功能,把源代码转成抽象语法树,进行遍历和生产,它本身也不知道具体要转换成什么语法,以及如何如何使用@babel/preset-env:可以转换js语法,预设是插件的集合@bable/cli:使用命令行才需要下载,否则在webpack这种工具中使用babel不需要安装这依赖包
- 先把Es6转换成es6语法树 (babel-core)
- 然后调用预设preset-env把ES6语法树转成ES5语法树(preset-env)
- 再把ES5语法树转成ES5代码(babel-core)
resolve
用于文件路径解析
SplitChunks(代码分离)
目的:将代码分离到不同的bundle中,之后可以按需加载,或者并行加载这些文件
问题:默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度
好处:代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能
module.exports = {
entry: path.resolve(__dirname, './src/main.js')
output: {
// 所有文件的输出路径
path: path.resolve(__dirname, './build'),
// 打包输出的入口文件命名
filename: "static/js/[name].js",
// 给打包输出的其他文件命名 ([name].chunk.js 是为了和主文件区分)
chunkFileName: 'static/js/[name].chunk.js',
// 图片、字体等通过type: asset处理资源命名方式
assetModuleFilename: "static/image/[hash:10][ext][query]"
},
optimization: {
splitChunks: {
// 如果是单页面应用把值修改为all,其他用默认值就好啦
chunks: 'all',
},
},
};
dynamic import (动态导入)
另外一个代码拆分的方式是动态导入时,使用ECMAScript中的 import() 语法来完成,动态导入通常是一定会打包成独立的文件的
动态导入的文件命名,通常会在output中,通过 chunkFilename 属性来命名
默认情况下我们获取到的 [name] 是和id的名称保持一致的,修改name的值,可以通过magic comments(魔法注释)的方式
import(/* webpackChunkName: "home" */)
module.exports = {
output: {
path: resolve('build'),
chunkFilename: 'chunk_[id]_[name].js',
},
};
Terser(代码压缩)
npm install css-minimizer-webpack-plugin -D (css压缩)
对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置,需要打开minimize,让其对我们的代码进行压缩(默认production模式下已经打开了)
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
rules: [
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin(),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "static/css/[name].css",
chunkFilename: "static/css/[name].chunk.css",
}),
new CssMinimizerPlugin()
],
};
Tree Shaking (打包时消除无用代码)
mode 为 production 模式下,webpack默认的一些优化会带来很大额影响
webpack实现Tree Shaking采用了两种不同的方案:
- usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
- sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
场景:将mode设置为development,配合Terser一起优化才有效果
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
usedExports: true,
minimize: true,
minimizer: [new TerserPlugin()]
},
};
CSS实现Tree Shaking
npm install purgecss-webpack-plugin -D
作用:删除未使用的CSS
- paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
- 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;
const glob = require("glob");
const PurgeCSSPlugin = require("purgecss-webpack-plugin");
module.exports = {
plugins: [
// CSS Tree Shaking
new PurgeCSSPlugin({
paths: glob.sync(`${path.resolve(__dirname, "./src")}/**/*`, { nodir: true }),
sadeList: function () {
return {
standard: ["html", "body"]
}
}
}),
],
};
Prefetch和Preload 预获取和预加载
在声明 import 时,使用下面这些内置指令,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
prefetch 指令相比,preload 指令有许多不同之处
- preload chunk 会在父 chunk 加载时,以并行方式开始加载(prefetch chunk 会在父 chunk 加载结束后开始加载)
- preload chunk 具有中等优先级,并立即下载 (prefetch chunk 在浏览器闲置时下载)
- preload chunk 会在父 chunk 中立即请求,用于当下时刻 (prefetch chunk 会用于未来的某个时刻)
通过魔法注释来指定或者通过插件(preload-webpack-plugin)