認識 webpack-chain、以及 loader、plugin 的簡單實現

什麼是webpack-chain?

webpack-chain 嘗試經過提供可鏈式或順流式的 API 建立和修改webpack 配置。API的 Key 部分能夠由用戶指定的名稱引用,這有助於 跨項目修改配置方式 的標準化。 應用一個鏈式 API 來生成和簡化 2-4 版本的webpack的配置的修改。css

爲何使用webpack-chain?

webpack 的核心配置的建立和修改基於一個有潛在難於處理的 JavaScript 對象。雖然這對於配置單個項目來講仍是 OK 的,但當你嘗試跨項目共享這些對象並使其進行後續的修改就會變的混亂不堪。html

咱們能夠用webpack-chain作什麼?

搭建可插拔的開發環境跟生產環境,靈活配置項目,完善定製化解決方案。jquery

webpack-chain入門

當你安裝了 webpack-chain, 你就能夠開始建立一個webpack的配置。下面的代碼示例來自於官方示例。基本配置 webpack.config.jswebpack

// 導入 webpack-chain 模塊,該模塊導出了一個用於建立一個webpack配置API的單一構造函數。
const Config = require('webpack-chain');

// 對該單一構造函數建立一個新的配置實例
const config = new Config();

// 用鏈式API改變配置
// 每一個API的調用都會跟蹤對存儲配置的更改。

config
  // 修改 entry 配置
  .entry('index')
    .add('src/index.js')
    .end()
  // 修改 output 配置
  .output
    .path('dist')
    .filename('[name].bundle.js');

// 建立一個具名規則,之後用來修改規則
config.module
  .rule('lint')
    .test(/\.js$/)
    .pre()
    .include
      .add('src')
      .end()
    // 還能夠建立具名use (loaders)
    .use('eslint')
      .loader('eslint-loader')
      .options({
        rules: {
          semi: 'off'
        }
      });

config.module
  .rule('compile')
    .test(/\.js$/)
    .include
      .add('src')
      .add('test')
      .end()
    .use('babel')
      .loader('babel-loader')
      .options({
        presets: [
          ['@babel/preset-env', { modules: false }]
        ]
      });

// 也能夠建立一個具名的插件!
config
  .plugin('clean')
    .use(CleanPlugin, [['dist'], { root: '/dir' }]);

// 導出這個修改完成的要被webpack使用的配置對象
module.exports = config.toConfig();
複製代碼

共享配置也很簡單。僅僅導出配置 和 在傳遞給webpack以前調用 .toConfig() 方法將配置導出給webpack使用git

若是你學習使用過jquery,那麼上面的鏈式調用,相信你能很快上手,可是咱們還要了解一下webpack-chain 中的核心API接口。github

  1. ChainedMap
  2. ChainedSet

** ChainedMap和ChainedSet的操做相似於JavaScript Map, 爲鏈式和生成配置提供了一些便利**web

詳細的示例介紹請查看下官方文檔,比較詳細,webpack-chain,接下來咱們看下,如何一步步的搭建咱們的生產環境配置。bash

目錄

│── build
│   │── config.js                 // 公共配置
│   │── build.js
│   └── dev.js
│── config
│   │── base.js                 // 基礎配置
│   │── css.js                  // css 配置
│   │── HtmlWebpackPlugin.js    // html 配置
│   └── MiniCssExtractPlugin.js // 提取css樣式
│── public                      // 公共資源
│   └── index.html              // html 模版
└── src                         // 開發目錄
    │
    │── index.css               //測試樣式
    └── main.js                // 主入口
複製代碼

build/base.jsbabel

const { findSync } = require('../lib');
const Config = require('webpack-chain');
const config = new Config();
const files = findSync('config');
const path = require('path');
const resolve = p => {
  return path.join(process.cwd(), p);
};

module.exports = () => {
  const map = new Map();

  files.map(_ => {
    const name = _.split('/')
      .pop()
      .replace('.js', '');
    return map.set(name, require(_)(config, resolve));
  });

  map.forEach(v => v());

  return config;
};
複製代碼

構建生產環境

build/build.jsapp

const rimraf = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const path = require('path');
rimraf.sync(path.join(process.cwd(), 'dist'));// 刪除 dist 目錄

const config = require('./config')();
const webpack = require('webpack');
const spinner = ora('開始構建項目...');
spinner.start();

webpack(config.toConfig(), function(err, stats) {
  spinner.stop();
  if (err) throw err;
  process.stdout.write(
    stats.toString({
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }) + '\n\n'
  );

  if (stats.hasErrors()) {
    console.log(chalk.red('構建失敗\n'));
    process.exit(1);
  }

  console.log(chalk.cyan('build完成\n'));
});
複製代碼

構建開發環境

build/dev.js

const config = require('./config')();
const webpack = require('webpack');
const chalk = require('chalk');
const WebpackDevServer = require('webpack-dev-server');
const port = 8080;
const publicPath = '/common/';

config.devServer
  .quiet(true)
  .hot(true)
  .https(false)
  .disableHostCheck(true)
  .publicPath(publicPath)
  .clientLogLevel('none');

const compiler = webpack(config.toConfig());
const chainDevServer = compiler.options.devServer;
const server = new WebpackDevServer(
  compiler,
  Object.assign(chainDevServer, {})
);

['SIGINT', 'SIGTERM'].forEach(signal => {
  process.on(signal, () => {
    server.close(() => {
      process.exit(0);
    });
  });
});
server.listen(port);// 監聽端口

new Promise(() => {
  compiler.hooks.done.tap('dev', stats => {
    const empty = ' ';
    const common = `App running at: - Local: http://127.0.0.1:${port}${publicPath}\n`;
    console.log(chalk.cyan('\n' + empty + common));
  });
});
複製代碼

css 提取 loader 配置

config/css.js

module.exports = (config, resolve) => {
  return (lang, test) => {
    const baseRule = config.module.rule(lang).test(test);
    const normalRule = baseRule.oneOf('normal');
    applyLoaders(normalRule);
    function applyLoaders(rule) {
      rule
        .use('extract-css-loader')
        .loader(require('mini-css-extract-plugin').loader)
        .options({
          publicPath: './'
        });
      rule
        .use('css-loader')
        .loader('css-loader')
        .options({});
    }
  };
};
複製代碼

css 提取插件 MiniCssExtractPlugin

config/MiniCssExtractPlugin.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (config, resolve) => {
  return () => {
    config
      .oneOf('normal')
      .plugin('mini-css-extract')
      .use(MiniCssExtractPlugin);
  };
};
複製代碼

自動生成 html

config/HtmlWebpackPlugin.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (config, resolve) => {
  return () => {
    config.plugin('html').use(HtmlWebpackPlugin, [
      {
        template: 'public/index.html'
      }
    ]);
  };
};
複製代碼

以上是基於webpack-chain的基本配置,接下來讓咱們嘗試本身動手寫一個基礎loader和plugin,動手以前,先回顧一下loader和plugin的基礎概念。

  • loader webpack 原生只支持 JS 和 JSON 兩種文件類型,經過 Loaders 去支持其它文件類型而且把他們轉化成有效的模塊。loader 的執行順序時從右往左,右邊的執行結果做爲參數傳到左邊。
  • plugin 插件用於 bundle 文件的優化,資源管理和環境變量注入,任何 loaders 沒法處理的事情能夠經過 plugins 完成,做用於整個構建過程。

自定義loader

options-chain-loader.js

module.exports = function(content) {
    //正則匹配,content爲loader加載的文件內容
  return content.replace(new RegExp(/([\$_\w\.]+\?\.)/,'g'),function(res) {
    let str  = res.replace(/\?\./,'');
    let arrs = str.split('.');
    let strArr = [];
    for(let i = 1; i <= arrs.length; i++) {
      strArr.push(arrs.slice(0,i).join('.')); 
    }
    let compile = strArr.join('&&');
    const done = compile + '&&' + str + '.'
    return  done;
  });
};
複製代碼

wepakc-chain配置中使用

module.exports = (config, resolve) => {
  const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
  const normalRule = baseRule.oneOf('normal');
  return () => {
    normalRule
      .use('options-chain')
      .loader(resolve('options-chain-loader'))
  }
}
複製代碼

自定義plugins

fileListPlugins.js

class FileListPlugin { 
  apply(compiler) {
    // console.log(compiler)
    //emit 是異步 hook,使用 tapAsync 觸及它,還可使用 tapPromise/tap(同步) 
     compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => { 
       // 在生成文件中,建立一個頭部字符串: 
       var filelist = 'In this build:\n\n'; 
       // 遍歷全部編譯過的資源文件, 
       // 對於每一個文件名稱,都添加一行內容。 
       for (var filename in compilation.assets) { 
        // console.log(filename)
         filelist += '- ' + filename + '\n'; 
        }
         // 將這個列表做爲一個新的文件資源,插入到 webpack 構建中: 
         compilation.assets['filelist.md'] = { 
           source: function() {
              return filelist; 
            }, 
            size: function() { 
              return filelist.length; 
            } 
          }; 
          callback(null,11); 
        }); 
      } 
    // compiler.plugin('done', function() {
    // console.log('Hello World!');
    // });
    // }
    // compiler.plugin("compilation", function(compilation) {

    // // 如今,設置回調來訪問 compilation 中的步驟:
    // compilation.plugin("optimize", function() {
    // console.log("Assets are being optimized.");
    // });
    // });
} 
module.exports = FileListPlugin;

複製代碼

wepakc-chain配置中使用

const FileListPlugin = require("../fileListPlugins");

module.exports = (config, resolve) => {
  return () => {
    config
      .plugin('file-list-plugin')
      .use(FileListPlugin)
  }
}
複製代碼
相關文章
相關標籤/搜索