手寫webpack
文章目錄
構建自定義命令
因爲這是單獨的全局功能模塊,源碼請移步至
https://github.com/smallMote/ypack.git
手寫loader
這裏以處理less文件實現簡易less-loader和style-loader
style-loader:將css源碼插入到head標籤中
function loader(source) {
return `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(source)};
document.head.appendChild(style);
`;
}
module.exports = loader;
less-loader:將less語法轉換成css語法
// loader依賴
yarn add less -D
const less = require('less');
// 將less轉換成css
function loader(source) {
let css = '';
less.render(source, (err, c) => {
css = c.css;
});
css = css.replace(/\n/g, '\\n');
return css;
}
module.exports = loader;
手寫插件(簡單實現)
在自定義插件中要有一個apply方法,接受一個compiler參數,也就是webpack執行對象,可以獲取webpack的生命週期。
// webpack生命週期
this.hooks = { // 鉤子函數(生命週期)
entryOption: new SyncHook(), // 入口配置鉤子
compile: new SyncHook(), // 編譯鉤子
afterCompile: new SyncHook(), // 編譯完成後鉤子
afterPlugins: new SyncHook(), // 插件執行後鉤子
run: new SyncHook(), // 執行打包
emit: new SyncHook(), // 文件發生
done: new SyncHook() // 結束
};
webpack.config.js
const EasyPlugins = require('./plugins/easy-plugins')
module.exports = {
...
plugins: [
new EasyPlugins()
],
...
}
手寫babel-loader
依賴:
yarn add @babel/core @babel/present-env loader-utils
webpack.config.js
module.exports = {
...
resolveLoader: { // 解析loader配置,引入loader必須是絕對路徑
alias: { // 別名
'babel-loader': path.resolve(__dirname, 'loader', 'babel-loader.js')
}
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'] // presets必須是數組格式
}
},
]
},
}
...
babel-loader.js
const babel = require('@babel/core');
const loaderUtils = require('loader-utils')
function loader(source) { // this -> loader處理器上下文對象
const optios = loaderUtils.getOptions(this); // 獲取loader配置
const cb = this.async(); // 異步工具,實現異步返回
babel.transform(source, {
...optios,
sourceMaps: true, // 源碼映射
}, function(err, result) {
cb(err, result.code, result.map)
})
}
module.exports = loader
實現file-loader
實現圖片引入路徑,可將圖片當做模塊引入,路徑使用hash值代替。
file-loader.js
const loaderUtils = require('loader-utils');
function loader(source) {
const filename = loaderUtils.interpolateName(this, '[hash].[ext]', { content: source }); // 根據內容產生hash形式的圖片路徑
this.emitFile(filename, source); // 發射文件
return `module.exports = '${filename}'`; // 當做模塊返回出去
}
loader.raw = true; // 轉換成2進制流
module.exports = loader
實現url-loader
file-loader
的擴展,增加將小於一定大小的圖片可轉換爲base64編碼,有利於瀏覽器緩存,減少請求服務器的次數。
依賴:file-loader
,mime
: 識別文件類型
yarn add mime
url-loader.js
const loaderUtils = require('loader-utils');
const mime = require('mime'); // 識別文件類型
function loader(source) {
let limit = loaderUtils.getOptions(this).limit;
if (limit && limit > source.length) {
// this.resourcePath 處理資源路徑
return `module.exports = "data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`;
} else {
return require('./file-loader').call(this, source);
}
}
loader.raw = true; // 轉換成2進制流
module.exports = loader
實現css-loader,改造style-loader
主要處理background: url(../1.png)
這種情況;思想:將代碼分割成三段:
第一段:url(…/1.png)之前的全部
第二段:url(…/1.png)
第三段:url(…/1.png)後的全部
然後使用代碼片段實現拼接,主要是插入到webpack
打包後的eval
函數中讓style-loader
處理。
css-loader.js
function loader(source) {
const reg = /url\((.+?)\)/g; // 匹配url(path)
let pos = 0;
let current = null; // 當前匹配後返回的內容
let codeArr = ['const list = []']; // 代碼片段存儲
while(current = reg.exec(source)) {
let [matchUrl, g] = current; // [url('../assets/1.png'), '../assets/1.png']
let last = reg.lastIndex - matchUrl.length;
codeArr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`); // 第一段
pos = reg.lastIndex;
codeArr.push(`list.push('url(' + require(${g}) + ')')`); // 第二段(替換成require語法)
}
codeArr.push(`list.push(${JSON.stringify(source.slice(pos))})`); // 第三段
codeArr.push(`module.exports = list.join('')`); // 轉換成字符串導出
return codeArr.join('\r\n');
}
module.exports = loader
style-loader.js
使用pitch直接拿到資源去處理,然後返回給css-loader處理,最後拿到css-loader處理後的結果。
流程:style-loader -> less-loader!css-loader -> index.less
// 新增pitch
loader.pitch = function pitch(remainingRequest) { // 剩餘的請求
return `
let style = document.createElement('style');
style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)}); // css處理好的路徑
document.head.appendChild(style);
`;
}