虽说不同构建工具在原理上是大同小异,但下一篇也应该写写 vite 了。
loader 主要的是处理静态资源,而 plugins 是可以贯穿在整个 webpack 构建的周期中,他能做到 loader 做不到的事情。但是,loader 他可以用独立的运行环境,可以在本地使用一些库进行本地发发调制,而 plugins 不行,他必须编写好这个 plugin 之后在 webpack 构建中将 plugin 放在 plugins 的数组中执行。
import { getOptions } from 'loader-utils'; import { validate } from 'schema-utils'; const schema = { type: 'object', properties: { test: { type: 'string', }, }, }; export default function (source) { const options = getOptions(this); validate(schema, options, { name: 'Example Loader', baseDataPath: 'options', }); // Apply some transformations to the source... return `export default ${JSON.stringify(source)}`; }
loader 实际上就是一个 js 模块,提供一个方法对原文件进行逻辑操作,处理完毕之后返回回去的一个过程。顺带提一点就是,loader 的链式调用是从后往前。
参数获取可以使用一个 叫 loader-utils 的 loader,使用其中的 getOptions 的方法就可以拿到传递的参数。
在 runLoaders 配置中 loaders 参照文档修改为带 options 的配置,举例加上一个对象:
runLoaders({ loaders: [ // path.join(__dirname, './loaders/raw-loader.js'), { loader: path.join(__dirname, './loaders/raw-loader.js'), options:{ env: 'development', version: '1.0.0' }, } ], ...})
之后使用 loader-utils 将传递的参数带出。注意,这里的 module.exports 不能使用箭头函数,否则,this 是指向了当前的作用域,就拿不到 runLoaders 里面的属性了。
// npm install loader-utils -save-dev const loaderUtils = require('loader-utils'); module.exports = function (source) { console.log(this); const { env, version } = loaderUtils.getOptions(this); console.log(env); console.log(version); ... }; // npmjs -> https://www.npmjs.com/package/loader-utils
同步执行的情况下,处理异常错误打印的方式有两种:
第一种:是直接使用 Error 输出错误,可以在信息内填写错误编号
module.exports = function (source) { ... return new Error('error:10001'); };
第二种:是直接使用 this.callback ,根据参数的不同,再执行下一步或者是输出报错信息。
module.exports = function (source) { ... return this.callback(new Error('error:10001'), source); };
this.callback 的第一个参数是 Error 时,表示异常直接报错,如果第一个参数是 null ,那就说明函数正常执行下一步返回数据, 同时也支持多个参数进行传递,代码如下:
module.exports = function (source) { ... const params = { key: 1 } return this.callback(null, source, params); };
使用异步获取结果需要使用 this.async() 这个方法,作为一个 callback 使用,第一个参数是判断是否错误,第二个直接传递参数。
module.exports = function (source) { ... const callback = this.async(); fs.readFile(path.join(__dirname, '../src/number.txt'), 'utf-8', (err, data) => { if(err){ callback(err,'error') } callback(null, data); }); };
使用 emitFile 进行输出。
const loaderUtils = require('loader-utils'); module.exports = function (source) { const url = loaderUtils.interpolateName(this, '[name].[ext]', source); this.emitFile(url, source) return source; }
首先,需要使用 spritesmith 这个依赖,将多张图片和合并到一起。
思路是先将 css 文件获取到,再使用正则匹配导出所有的图片地址
const loaderUtils = require('loader-utils'); module.exports = function (source) { const images = source.match(/url\((\S*)/g); const matchedImages = []; if (images && images.length > 0) { for (let i = 0; i < images.length; i++) { const img = images[i].match(/url\(..(\S*)\'/)[1]; matchedImages.push(path.join(__dirname, img)); } } }
再把文件的目录提取出来,转换成一个绝对地址 push 到一个数组里面,打印出来的 matchedImages 如下:
[ '/Users/../images/web-security-bg-1.jpeg', '/Users/../images/webpack-images-2.png' ]
Spritesmith.run({ src: matchedImages}, (err, result) => { fs.writeFileSync(path.join(process.cwd(), 'dist/sprite.png'), result.image); source = source.replace(/url\((\S*)/g, (match) =>{ return `url("dist/sprite.jpg")`; }); fs.writeFileSync(path.join(process.cwd(), 'dist/index.css'), source); callback(null,source); })
在 dist 的目录中,就会出现一个合并好的图片同时 dist 里面还有一个已替换了 sprite 图的 css 文件 ,当然这里只是说明了一个思路,如果要完全的实现图片和样式的替换还需要考虑到背景大小,定位或者是一些边界问题,这里就不再细说了。
// webpack writting a plugin -> https://webpack.js.org/contribute/writing-a-plugin/ // A JavaScript class. class HelloWorldPlugin { apply(compiler) { compiler.hooks.done.tap('Hello World Plugin', ( stats /* stats is passed as an argument when done hook is tapped. */ ) => { console.log('Hello World!'); }); } } module.exports = HelloWorldPlugin;
plugin 实际上一个一个类,在 webpack 使用 plugin 的时候都会有 new XXplugin() 的操作,就是新建一个 plugin 的类。apply 是 plugin 在 webpack 是每一次构建的时候都会运行。
hooks 是 compiler 对象的一个钩子,也可以说是可以监听在某个阶段做一些什么样的事情。会掉的最后就是这个 plugin 的逻辑代码。
首先,还是先使用一个 jszip 它可以将文件压缩成一个 zip 包,使用 compiler 对象的 hooks 的 emit 钩子,生成一个文件。
const JSZip = require('jszip'); const zip = new JSZip(); compiler.hooks.emit.tapAsync('ZipPlugin', (Compilation, callback) => { const folder = zip.folder(this.options.filename); for (let filename in Compilation.assets) { const source = Compilation.assets[filename].source(); folder.file(filename, source) } })
使用 zip.folder 先设置 zip 包的名称, 在触发到了emit 的 tapAsunc 异步钩子的时候,处理 compilation 的内容填充到 folder 里面去。 compilation.assist 是一个目录名,遍历出文件名之后使用 source 拿到 source 在放回 folder 中。
zip.generateAsync({ type:'nodebuffer' }).then((content)=>{ const outputPath = path.join(Compilation.options.output.path ,this.options.filename + '.zip'); const outputRelativePath = path.relative( Compilation.options.output.path, outputPath ) Compilation.assets[outputRelativePath] = new RawSource(content); callback() console.log(Compilation.options) }) })
使用 zip.generateAsync 返回的 content 是一个 buffer ,需要使用 RawSource 将这个 buffer 转换挂到 compilation 的 assist 中 ,最后执行 callback 。
近几年,互联网行业蓬勃发展,在互联网浪潮的冲击下,互联网创业已成为一种比较...
想了解更多内容,请访问: 51CTO和华为官方战略合作共建的鸿蒙技术社区 https://...
TIOBE 公布了 2021 年 3 月的编程语言排行榜。 本月 TIOBE 指数没有什么有趣的变...
背景 我们知道 如果在Kubernetes中支持GPU设备调度 需要做如下的工作 节点上安装...
在Python开发过程中,我们难免会遇到多重条件判断的情况的情况,此时除了用很多...
基本介绍 给定 n 个权值作为 n 个叶子节点,构造一颗二叉树,若该树的带权路径长...
溢价 域名 的续费价格如何?通常来说,因为溢价域名的价值高于普通域名,所以溢...
前言 统计科学家使用交互式的统计工具(比如R)来回答数据中的问题,获得全景的认...
本文转载自微信公众号「bugstack虫洞栈」,作者小傅哥 。转载本文请联系bugstack...
本文转载自公众号读芯术(ID:AI_Discovery)。 这一刻你正在应对什么挑战?这位前...