1. webpack loader
loader官方解释是文件预处理器,通俗点说就是webpack在处理静态文件的时候,需要使用 loader 来加载各种文件,比如: js文件需要使用babel-loader,html文件需要使用html-loader,css文件需要使用css-loader、style-loader等等。
loader编写原则
- 单一原则: 每个loader只做一件事,使每个loader易维护,也可以在更多场景链式调用;
链式调用: Webpack会从右到左(或从下到上)按顺序链式调用每个loader;
- 最后的 loader 最早调用,将会传入原始资源内容,期望值是传出 code、 sourceMap(可选) 和 data(可选,例如AST)
当 loader 需要返回多个结果时,必须返回undefined,使用
this.callback(error: Error | null, source: string | Buffer, sourceMap?: SourceMap, data?: any);
- 第一个 loader 最后调用,会传入前一个 loader 输出的结果
顺序会受enforce: pre -> normal(默认) -> inline -> post影响
- 统一原则: 遵循Webpack制定的设计规则和结构,输入与输出均为字符串,各个loader完全独立,即插即用;
loader实例
代码地址
在编写类似vant的移动端组件库时,作为展示用的vue页面其实是用md文档转的,这样做的好处是让markdown文档复用,同时兼顾页面展示和描述文件,自己参照@vant/markdown-loader写了个md-parser-loader,效果如图所示
中间那部分页面其实只是个README.md文档
router.js
动态加载markdown文件而不是vue文件
const routes = [
{
name: 'home',
path: '/',
component: () => import('../../README.md')
},
...__config__.nav.map(i => i.items).flat().map(i => ({
name: i.path,
path: `/${i.path}`,
component: () => import(`@/${i.path}/README.md`)
}))
];
webpack.config.js
module.exports = {
//...
module: {
rules: [
{
test: /\.md$/,
use: [
'vue-loader',
require.resolve('./md-parser-loader')
]
}
]
}
};
md-parser-loader/index.js
更多关于markdown的规则可以查看这篇文章编写markdown-it的插件和规则
const { getOptions } = require('loader-utils');
const MarkdownIt = require('markdown-it');
const CardWrapper = require('./card-wrapper');
const highlight = require('./highlight');
function wrapper(content) {
content = CardWrapper(content);
content = escape(content);
return `
<template>
<section v-html="content" v-once />
</template>
<script>
export default {
created() {
this.content = unescape(\`${content}\`);
},
};
</script>
`;
}
module.exports = function (source) {
// loader-utils包提供了许多有用的工具,但最常用的是获取传递给loader的选项
const options = getOptions(this) || {};
const parser = new MarkdownIt({
html: true,
highlight,
...options
});
return wrapper(parser.render(source));
};
md-parser-loader/highlight.js
// 标签code代码块高亮
const hljs = require('highlight.js');
module.exports = function highlight(str, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
}
return '';
};
md-parser-loader/card-wrapper.js
// 美化h3标签 转为卡片式
module.exports = function cardWrapper(html) {
const group = html.replace(/<h3/g, ':::<h3').replace(/<h2/g, ':::<h2').split(':::');
return group.map(fragment => {
if (fragment.indexOf('<h3') !== -1) {
return `<div class="card">${fragment}</div>`;
}
return fragment;
}).join('');
};
2. webpack plugin
plugin监听Webpack运行生命周期的所有事件,在合适的时机通过Webpack提供的API改变输出结果。大家熟知的BundleAnalyzerPlugin即是监听done事件后分析各个打包文件的大小。
plugin由以下组成:
- 一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调。
Compiler 和 Compilation
compiler
对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。compilation
对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
plugin实例
在实践Hybrid App离线包的过程中,需要监听emit钩子(after生成资源&before输出到目录),在每次compilation版本构建时遍历对象的assets属性读取资源并记录在一个资源映射的json文件里。App端通过拦截该资源对应的服务端请求(remoteUrl),并根据相对路径(path)从本地命中相关资源然后返回,以避免webview请求远程静态资源达到优化白屏时间的目的。
资源JSON图示
打包结果图示
offline-package-plugin/index.js
const { lookup } = require('mime-types');
class OfflinePackagePlugin {
constructor(options) {
this.options = Object.assign({
packageName: 'packageName', // 包名,区分不同项目
version: +new Date(), // 版本号,若不同,进行bsdiff算法计算出差分文件列表
baseUrl: 'http://localhost:8080/', // 静态资源域名
fileTypes: ['html', 'js', 'css', 'png'], // 需要配置的静态资源后缀
}, options);
}
apply(compiler) {
compiler.hooks.emit.tapAsync('OfflinePackagePlugin', (compilation, callback) => {
const content = {
packageName: this.options.packageName,
version: this.options.version,
items: []
};
for (const filename in compilation.assets) {
if (
!this.options.fileTypes.some(item => {
return new RegExp(`\\.${item}$`).test(filename)
})
) {
continue;
}
content.items.push({
packageName: this.options.packageName,
version: this.options.version,
remoteUrl: this.options.baseUrl + filename,
path: filename,
mimeType: lookup(filename)
});
}
// stringify tab: 2
const outputFile = (manifest => {
return JSON.stringify(manifest, null, 2);
})(content);
// create offline-package.json
compilation.assets['offline-package.json'] = {
source: () => {
return outputFile;
},
size: () => {
return outputFile.length;
}
};
// todo: add option isNeedGzip to compress
callback();
});
}
};
module.exports = OfflinePackagePlugin;
webpack.config.js
const OfflinePackagePlugin = require('./offline-package-plugin');
module.exports = {
//...
plugins: [
new OfflinePackagePlugin({ packageName: 'demo' })
]
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。