webpack
手写Loader
注意:loader 的执行是从后 ->前。从右 -> 左
// loader就是一个函数
module.exports = function (source) {
// return source.replace('loader', 'webpack')
return source.replace('loader', this.query.name)
// this.query获得options 传来的数据
}
//loader 可以通过 options 对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。
- loader函数一定得是声明式函数,不能是箭头函数,因为webpack在运行loader的时候,会涉及到this指向的变更,箭头函数的话,是不满足的
- loader 函数接受source为一个参数,意思是源代码,我们拿到原代码后,就可以对源代码做一些相应的操作了
使用自己写的loader
module.exports = {
module: {
rules: [
{
test: /\.js/,
use: [path.resolve(__dirname, './loaders/replaceLoader.js')]
options: {
name: 'hello'
}
}
]
},
}
const loaderUtils = require('loader-utils')
module.exports = function (source) {
const options = loaderUtils.getOptions(this)
// loaderUtils.getOptions(this) 获取option 传过来的参数
const result = source.replace('loader', options.name)
this.callback(null, result)
// callback,我们看上面的代码,只是返回了一个替换后的结果,想返回其他值的话,我们就需要用到callback了
}
// callback 有四个参数
this.callback(
err: Error | null, // 错误信息
content: string | Buffer, // 最终生成的源码
sourceMap?: SourceMap, // 对应的sourcemap
meta?: any // 其他额外的信息
);
在loader里面写异步
const loaderUtils = require('loader-utils')
module.exports = function (source) {
const options = loaderUtils.getOptions(this)
const callback = this.async()
setTimeout(() => {
const result = source.replace('loader', options.name)
// this.callback(null, result)
callback(null, result) // 实际上为this.callback
}, 1000)
}
resolveLoader
我们现在使用自定义的loader还是采用的,很长的一段绝对路径来应用的,但实际上,我们更希望像使用第三方loader那样,只引用一个loader名字,
我们知道,webpack 默认会到 node_modules 里面找对应的loader,这样不方便我们调试,我们可以通过给webpack.config.js添加resolveLoader属性,将loader指向我们创建的loaders文件夹
resolveLoader: {
modules: [
path.resolve(__dirname, "node_modules"),
path.resolve(__dirname, "./loaders"),
],
}
意识是,当你去引用一个loader的时候,会优先到node_moudules里面去找,找不到才会去我们自己的loader文件下面去找,这样配置后,我们的自定义loader就可以这样配置了
module.exports = {
module: {
rules: [
{
test: /\.js/,
use: [{
loader: 'loaders/replaceLoader',
options: {
name: 'hello'
}
}]
}
]
}
校验配置参数是否正确 schema-utils
const fs = require("fs");
const {resolve} = require("path");
const loaderUtils = require("loader-utils");
const { validate } = require("schema-utils");
module.exports = function (source) {
// 获取配置参数
let options= loaderUtils.getOptions(this);
let schema = {
type: "object",
properties: {
text: {type: "string"}
}
}
// 校验参数是否正确
validate(schema, options,"banner-loader")
return `${options.text} ${source}`;
}
SourceMap
SourceMap是一种映射关系。当项目运行后,如果出现错误,错误信息只能定位到打包后文件中错误的位置。如果想查看在源文件中错误的位置,则需要使用映射关系,找到对应的位置
如果在开发环境下:
mode: "development"
devtool: "cheap-module-eval-source-map"
// 提示方式比较全面,同时打包速度也是比较快的
// 生产环境下一般不用配置SourceMap,但如果出了问题想要提示,如下配置
mode: "production"
devtool: "cheap-module-source-map"
oneOf
webpack原本的loader是将每个文件都过一遍,比如有一个js文件 rules中有10个loader,第一个是处理js文件的loader,当第一个loader处理完成后webpack不会自动跳出,而是会继续拿着这个js文件去尝试匹配剩下的9个loader,相当于没有break。而oneOf就相当于这个break
rules:[
oneOf:[
{
test:/\.css$/,
use:[...common_css_loader]
},
{
test:/\.less$/,
use:[...common_css_loader,'less-loader']
},
{
test:/\.html/,
loader:'html-loader'
}
]
]
enforce
loader的执行顺序是从下往上的,但是有时候我们想先执行某个loader 就要把它移到最后边这样非常的不方便。
enforce的作用是设置loader的优先级
enforce有以下几个配置项
- pre 优先处理
- normal 正常处理(默认)
- inline 其次处理
- post 最后处理
执行loader的时候会根据enforce的配置来安排顺序,如果设置了pre则会优先执行
resourceQuery
用来测试通过import/require引入的模块参数是否符合要求
// 使用resource方式配置,还有or、and、not属性可以配置,完整的如下
// 匹配规则
module: {
rules: [{
use: ['babel-loader],
resource: {
test: /\.js$/,
exclude: [],
include: [],
not: [],
and: [],
or: []
}
}]
}
optimization
optimization.splitChunks
/**
* webpack中实现代码分割的两种方式:
* 1.同步代码:只需要在webpack配置文件总做optimization的配置即可
* 2.异步代码(import):异步代码,无需做任何配置,会自动进行代码分割,放置到新的文件中
*/
optimization: {
splitChunks: {
chunks: "all", //async异步代码分割 initial同步代码分割 all同步异步分割都开启
minSize: 30000, //字节 引入的文件大于30kb才进行分割
//maxSize: 50000, //50kb,尝试将大于50kb的文件拆分成n个50kb的文件
minChunks: 1, //模块至少使用次数
maxAsyncRequests: 5, //同时加载的模块数量最多是5个,只分割出同时引入的前5个文件
maxInitialRequests: 3, //首页加载的时候引入的文件最多3个
automaticNameDelimiter: '~', //缓存组和生成文件名称之间的连接符
name: true, //缓存组里面的filename生效,覆盖默认命名
cacheGroups: { //缓存组,将所有加载模块放在缓存里面一起分割打包
vendors: { //自定义打包模块
test: /[\\/]node_modules[\\/]/,
priority: -10, //优先级,先打包到哪个组里面,值越大,优先级越高
filename: 'vendors.js',
},
default: { //默认打包模块
priority: -20,
reuseExistingChunk: true, //模块嵌套引入时,判断是否复用已经被打包的模块
filename: 'common.js'
}
}
}
}
ptimization.runtimeChunk
runtimeChunk作用是为了线上更新版本时,充分利用浏览器缓存,使用户感知的影响到最低
Preload和Prefetch的区别
- 一个预加载块(preload)开始与父块并行加载。预取的块(prefetch)在父块完成加载后启动。
- 预加载块(preload)具有中等优先级,可以立即下载。而预取块(prefetch)在浏览器空闲时下载预取的块。
- 一个预加载的块(preload)应该被父块立即请求。预取的块(prefetch)可以在将来的任何时候使用。
- 浏览器的支持能力是不同的。