loader的开发
loader是一个函数,接收资源作为参数,返回资源
// raw-loaderconst loaderUtils = require('loader-utils');module.exports = function (source) {const json = JSON.stringify(source).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')return `export default ${json}`;};
调试
开发loader时候可以使用loader-runner工具进行方便的开发和调试,它允许不安装webpack的情况下运行loader。
如果希望在要构建的项目中测试自己的loader,可以配置webpack的resolveLoader,指定loader的目录
// webpack.config.jsmodule.exports = {// other config......resolveLoader:{modules: ['node_modules','loader']}};
开发
1. 传参
可以通过loader-utils的getOptions方法获取参数。
// raw-loaderconst loaderUtils = require('loader-utils');module.exports = function (source) {const {name} = loaderUtils.getOptions(this);console.log(name);const json = JSON.stringify(source).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')return `export default ${json}`;};
2. 异常处理
- throw new Error() 。
- this.callback(这个方法处理可以处理异常,也可以用来返回结果) 。
this.callback(err: Error | null,content: String | Buffer,sourceMap?: SourceMap,meta?: any);
// raw-loaderconst loaderUtils = require('loader-utils');module.exports = function (source){const {name} = loaderUtils.getOptions(this);console.log(name);const json = JSON.stringify(source).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')this.callback(null, json)};
3. 异步处理
this.async()方法返回结果。
4. 在loader中使用缓存
webpack默认使用缓存,可以通过this.cacheable(false)关掉缓存。
缓存条件:loader在相同输入情况下有相同输出。
有依赖的的loader无法使用缓存。
5. 文件输出
this.emitFile进行文件输出
const loaderUtils = require('loader-utils');module.exports = function (content) {const url = loaderUtils.interpolateName(this, '[hash].ext', {content});this.emitFile(url, content);const path = `__webpack_public_path__${JSON.stringify(url)}`;return `export default ${path}`;};
示例
开发一个table-loader
// config.tblcomponent/attribute color disabledialog red trueform blue false
// index.jsimport config, {name} from './config.tbl';console.log(name, config); // component/attribute {"dialog":{"color":"red","disable":"true"},"form":{"color":"blue","disable":"false"}}
loader代码
module.exports = source => {const table = {};const [head, ...contentList] = source.split('\n');const [tableName, ...columnList] = head.split(/\s+/g);contentList.forEach(content => {const [row, ...itemList] = content.split(/\s+/g);table[row] = {};itemList.forEach((item, index) => table[row][columnList[index]] = item);});return `export const name = '${tableName}';export default ${JSON.stringify(table)};`;};
使用
{test: /\.tbl$/,loader: 'table-loader'}
插件的开发
调试
插件没有想loader那样的独立运行环境(loader-runner),只能在webpack中运行,开发插件时候,可以使用webpack搭建最简单的环境来进行开发和调试。
开发
1. 插件的基本结构
class MyPlugin {apply(compiler) {compiler.hooks.done.tap('My Plugin', stats => {//});}}module.exports = MyPlugin;
2. 插件中的参数获取
在构造函数中获取。
class MyPlugin {constructor(options) {this.options = options;}}module.exports = MyPlugin;
3. 插件中的错误处理
参数校验阶段可以通过throw error方式抛出错误。
如果进入到hooks回调的执行环境中,则可以通过compilation对象的errors和warning收集。
compilation.errors.push('file not exist');
4. 写入文件
通过compilation.emitAsset()方法实现文件写入。
https://webpack.docschina.org/api/compilation-object/#emitasset
示例
防止倒转依赖,配置依赖的顺序,插件会检测出后面的依赖前面的情况并报错,构建会失败。
例如
├── index.js└── lib└── util.js
其中代码如下
// index.jsimport util from './lib/util';console.log(util);export default 'index';
// util.jsimport index from '../index';export default 'util';
构建结果:
<font style="color:#E8323C;">ERROR in dependency reverse. /Users/mac/demo/plugin/src/lib/util.js should not dependend on /Users/mac/demo/plugin/src/index.js</font>
插件实现:
function ForbidReverseDependent(options) {this.order = options.order;}ForbidReverseDependent.prototype.apply = function(compiler) {var me = this;function findIndex(modulePath) {let index = -1;let maxLen = 0;me.order.forEach((item, i) => {if (new RegExp(`^${item}`).test(modulePath) && item.length > maxLen) {maxLen = item.length;index = i;}});return index;}compiler.hooks.compilation.tap('ForbidReverseDependent', compilation => {compilation.hooks.optimizeModules.tap('ForbidReverseDependent', (modules) => {for (let module of modules) {const moduleResource = module.resource;const moduleIndex = findIndex(moduleResource);if (moduleIndex === -1) {continue;}for (let dependency of module.dependencies) {const depModule = compilation.moduleGraph.getModule(dependency);if (!depModule || !depModule.resource) {continue;}const depResource = depModule.resource;const depIndex = findIndex(depResource);if (depIndex !== -1) {if (depIndex > moduleIndex) {compilation.errors.push(`dependency reverse. ${moduleResource} should not dependend on ${depResource}`);}}}}});});}
