webpack學習記錄(第三階段)

手寫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);
`;
}

源碼:https://github.com/smallMote/webpack/tree/npm-package

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章