Webpack 預處理器 loader

工程中,咱們須要和 HTML、CSS、模板、圖片和字體等打交道,那如何處理這些這類靜態資源呢?css

其實在 webpack 的眼中,這些靜態資源都是模塊。webpack 自己只認識 js,其餘類型資源必須預先定義一個或多個 loader 轉譯,輸出爲 webpack 能接收的形式在繼續進行處理,因此說 loader 作的就是預處理工做。html

好比組件 js 中加載該組件須要的 css 文件(實現高內聚,若是都在全局引入組件css 文件,哪天去掉這個組件 js,還要同時去掉組件 css 文件),其實只是表達二者之間的依賴關係,由於 css 最終仍是會打包到輸出資源目錄下,對 js 沒有任何實質性影響。vue

loader 概述

每一個 loader 本質都是一個函數,output = loader(input)。在 webpack4 以前,input 和 output 都必須爲字符串,而 webpack4 以後,也支持**抽象語法樹(AST)**的傳遞,那 loader 就能夠是鏈式的了,即 output = loaderA(loaderB(input))node

  • input,多是工程源文件字符串,多是上一個loader 的轉化結果(字符串、source map 或 AST 對象);
  • output,同 input 同樣,若是是最後一個 loader 就將結果給 webpack 後續處理;

注意,第一個 loader 的輸入時源文件,以後全部 loader 的輸入是上一個 loader 的輸出,最後一個 loader 輸出給 webpack。webpack

loader 結構解讀

module.exports = function loader(content, map, meta) {
  var callback = this.async();
  var result = handler(content, map, meta);
  callback(
    null, // err
    result.content, // 轉換後的 內容
    result.map, // 轉換後的 source-map
    result.meta // 轉換後的 AST
  );
};
複製代碼

從上面可看出,本質是個函數,功能是將收到後的內容進行轉換,而後返回轉換後的結果,可能有 source-map 和 AST 對象。git

loader 配置

css-loader

一個組件中會 import './index.css',那 webpack 怎麼處理呢?這就須要 css-loader 這個包來轉譯。github

1.安裝包web

npm install css-loader -D
複製代碼

2.配置正則表達式

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: ['css-loader'] }
    ]
  }
};
複製代碼
  • rules,模塊處理規則;
  • test,可接收一個正則表達式或一個元素爲正則表達式的數組;
  • use,字符串或數組,配置使用的 loader;

style-loader

css-loader 處理 css 的各種加載語法,而後可交給 style-loader 來將樣式字符串包裝成 style 標籤插入頁面。typescript

1.安裝包

npm install style-loader -D
複製代碼

2.配置

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] }
    ]
  }
};
複製代碼

style-loader 放在 css-loader 後面是由於 webpack 打包機制是按照數組從後往前的順序將資源交個 loader 處理。

exclude 與 include

  • exclude 用來排除指定目錄下的模塊,即下面 node_modules 中的模塊不會執行這條規則,該配置項一般是必加的,不然會拖慢總體打包速度
  • include 用來包含指定目錄下的模塊;
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
        exclude: /node_modules/
      }
    ]
  }
};
複製代碼

注意,exclude 和 include 都存在時,exclude 優先級高。

resource 和 issuer

二者都是用於更加精確地肯定模塊規則的做用範圍。使用頻率不高。二者關係以下:

好比組件 import './index.css',能夠這麼理解:被加載模塊是 resource,加載方就是 issuer。一般 css 配置的加載方是全局的,如今咱們要限定配置。

module.exports = {
  module: {
    rules: [
      {
        use: ['style-loader', 'css-loader'],
        resource: {
          test: /\.css$/,
          exclude: /node_modules/
        },
        issuer: {
          test: /\.js$/,
          exclude: '/node_modules/',
          include: '/src/pages/'
        }
      }
    ]
  }
};
複製代碼

enforce

用來指定一個 loader 的種類,其默認值爲 normal,可選值爲

  • pre,在 use 配置的全部 loader 以前執行,好比下面就是保證檢測的代碼不是其餘 loader 更改過來的;
  • post,在 use 配置的全部 loader 以後執行;
  • inline,官方不推薦使用;
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        enforce: 'pre',
        use: 'eslint-loader'
      }
    ]
  }
}
複製代碼

babel-loader

其功能是用來將 ES6+ 編譯爲 ES5,從而沒必要關注 ES6+ 特性在各平臺不兼容問題。

1.安裝包

npm install babel-loader @babel/core @babel/preset-env -D
複製代碼

2.配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: '/node_modules/',
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            presets: [
              [ 'env', { modules: false } ]
            ]
          }
        }
      }
    ]
  }
};
複製代碼
  • exclude,排除了目錄 node_modules,可不編譯 node_modules 目錄中的模塊,提升打包速度;
  • babel-loader,轉譯 ES6+;
  • @babel/core,babel 編譯器核心模塊;
  • @babel/preset-env,預置器,根據用戶配置的目標環境自動添加須要的插件和補丁來編譯 ES6+;
  • cacheDirectory,緩存機制,這裏設爲 true,在重複打包未改變的模塊時防止二次編譯,提升打包速度,指向 node_modules/.cache/babel-loader
  • presets 的 modules 設置爲 false,意思是禁止讓 @babel/preset-env 將模塊語句轉換,讓 ES6 Module 語法給 webpack 處理,如果爲 true,會將 ES6 Module 模塊轉化爲 CommonJS 形式,這將會致使 tree-shaking 特性失效;

注意,babel-loader 支持從 .babelrc 文件讀取 babel 配置,可將 presets 和 plugins 從配置中提取出來。

html-loader

將 HTML 文件轉化爲字符串並進行格式化,而後將 HTML 片斷經過 JS 加載進來。

1.安裝包

npm install html-loader -D
複製代碼

2.配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.html$/,
        use: 'html-loader'
      }
    ]
  }
};
複製代碼

ts-loader

1.安裝包

npm install ts-loader typescript -D
複製代碼

2.配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  }
};
複製代碼

注意,typescript 的配置不是在 ts-loader 中,而是在工程目錄的 tsconfig.json 中,相似

{
  "compilerOptions": {
    "target": "es5",
    "sourceMap": true
  }
}
複製代碼

file-loader

打包文件類型文件,並返回到 output.publicPath 中。

1.安裝包

npm install file-loader -D
複製代碼

2.配置

module.exports = {
  entry: './app.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js',
    publicPath: './assets/'
  },
  rules: [
    {
      test: /\.(png|jpg|jpeg|webp|gif)$/,
      use: {
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          // publicPath: './new-assets/'
        }
      }
    }
  ]
};
複製代碼

3.組件

import avatar from './assets/avatar.jpg';
console.log(avatar); // ./assets/xxxxxxx.jpg
複製代碼

注意,配置中 file-loader 的 options.publicPath 會覆蓋 output.publicPath,優先級高些。

url-loader

和 file-loader 做用相似,區別在於 url-loader 用戶能夠設置一個文件大小的閾值,若大於閾值其返回和 file-loader 同樣,若小於閾值返回文件以 base64 形式編碼。

1.安裝包

npm install url-loader -D
複製代碼

2.配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|jpeg|webp|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10240,
            name: '[name].[ext]',
            publicPath: './assets/'
          }
        }
      }
    ]
  }
};
複製代碼

注意,options 參數多了 limit 。

vue-loader

咱們知道 vue 組件包含模板、js 和樣式。vue-loader 用來將模板、JS 和樣式拆分。因此還得額外安裝另外的預加載器。

  • vue-template-compiler 來編譯模板;
  • css-loader 處理樣式;

1.安裝包

npm install vue vue-loader vue-template-compiler css-loader -D
複製代碼

2.配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader'
      }
    ]
  }
};
複製代碼

自定義 loader

說了有表明性的幾個 xxx-loader,有時候咱們須要改寫或新建本身的 loader 來實現本身的一些願望或目的。好比如今要實現給全部 JS 文件啓動嚴格模式。

簡單版本

1.新建目錄 abc-strict-loader,爲何是這麼名稱?後面安裝這個包後,從node_modules 中方便找,就在前面嘛; 2.進入 abc-strict-loader 目錄; 3.npm 初始化 npm init -y 4.新建文件 index.js;

module.exports = function(content) {
  // 處理 content
  var useStrict = "// 這是 abc-strict-loader \n'use strict';\n\n";
  return useStrict + content;
};
複製代碼

5.按說咱們應該先發布到 npm 這樣的社區,而後在項目中在安裝這個包,但是每當更改下就要重複上面 2 步,着實不是好辦法。這時可使用 npm 或 yarn 的軟鏈功能進行本地調試,等到符合咱們的預期需求後再發布到 npm 或 yarn 社區; 6.在項目工程目錄安裝 abc-strict-loadernpm install ./abc-strict-loader,此時項目的 node_modules 中會建立一個指向實際 abc-strict-loader 目錄的軟鏈,也就是說後面直接修改 abc-strict-loader,那項目中的依賴 abc-strict-loader 也會有效; 7.更改 webpack.config.js 配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'abc-strict-loader'
      }
    ]
  }
};
複製代碼

8.啓動服務,查看,而後改動 abc-strict-loader,而後再看下 node_modules/abc-strict-loader 是否發生變化;

加入緩存

若是文件和依賴包都沒有更改,那 loader 就直接使用緩存,而不是重複轉換。更改 abc-strict-loader/index.js

module.exports = function(content) {
  // 判斷緩存
  if (this.cacheable) {
    console.log('緩存');
    this.cacheable();
  }

  // 處理 content
  var useStrict = "// 這是 abc-strict-loader \n'use strict';\n\n";
  return useStrict + content;
};
複製代碼

獲取配置 options

1.首先,更改配置文件 webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'abc-strict-loader',
          options: {
            sourceMap: true
          }
        }
      }
    ]
  }
};
複製代碼

options 加入了 sourceMap:true

2.更改 abc-strict-loader,這裏爲了獲取 webpack.config.js 中的配置,須要安裝包 loader-utils

const loaderUtils = require('loader-utils');

module.exports = function(content) {
  // 判斷緩存
  if (this.cacheable) {
    console.log('緩存');
    this.cacheable();
  }

  // source-map
  var options = loaderUtils.getOptions(this) || {};
  console.log('abc-strict-loader options: ', options);  // abc-strict-loader options: { sourceMap: true }

  // 處理 content
  var useStrict = "// 這是 abc-strict-loader \n'use strict';\n\n";
  return useStrict + content;
};
複製代碼

控制檯你會看到關於 webpack.config.js 文件中關於 options 的打印信息。

使用 loaderUtils.getOptions 獲取配置對象,接下來就要展現真正 source-map 功能了。source-map 便於開發者在瀏覽器查看源代碼。若是沒有對 source-map 處理,最終也生成不了 map 文件,那在瀏覽器 devtool 中可能會看到錯誤的源碼。

3.安裝依賴包 source-map。進入 abc-strict-loader 目錄。

npm install source-map
複製代碼

4.繼續更改 abc-strict-loader 文件。

const loaderUtils = require('loader-utils');
const SourceNode = require('source-map').SourceNode;
const SourceMapConsumer = require('source-map').SourceMapConsumer;

module.exports = function(content, sourceMap) {
  const useStrict = "// 這是 abc-strict-loader \n'use strict';\n\n";

  // 判斷緩存
  if (this.cacheable) {
    console.log('緩存');
    this.cacheable();
  }

  // 支持 source-map
  const options = loaderUtils.getOptions(this) || {};
  if (options.sourceMap && sourceMap) {
    const currentRequest = loaderUtils.getCurrentRequest(this);
    const node = SourceNode.fromStringWithSourceMap(
      content,
      new SourceMapConsumer(sourceMap)
    );
    node.prepend(useStrict);

    const result = node.toStringWithSourceMap({
      file: currentRequest
    });
    const callback = this.async();
    callback(null, result.code, result.map.toJSON());
  }

  // 不支持 source-map
  return useStrict + content;
};
複製代碼
  • 參數中新增 sourceMap 對象,它是由 webpack 或上一個 loader 傳遞下來的,只有它存在 loader 才能繼續向下處理;
  • 經過依賴包 source-map 對 map 進行操做,接收和消費以前的文件內容和 source-map,對內容進行修改,而後產生新的 source-map;
  • 使用 this.async 獲取 callback 函數,callback 參數分別是拋出錯誤、處理後的源碼和新的 source-map;

完整代碼可查看目錄 09 =>O(∩_∩)O~

上一篇:從哪兒來到哪兒去

相關文章
相關標籤/搜索