AST定義了代碼的結構,經過操縱這顆語法樹,能夠精準的定位到聲明語句、賦值語句、運算語句等等,實現對代碼的分析、優化、變動等操做,主要有如下用途:javascript
JavaScriptParser是把js源碼轉化爲抽象語法樹的解析器。經常使用的JavaScript Parser:css
其中webpack就是使用的acorn將源代碼解析成AST進行操做。html
loader是webpack用來處理加載不一樣資源文件的插件,它只在webpack對資源文件進行加載階段使用。java
從前面的文章webpack由淺入深——(webapck簡易版)能夠知道,loader的本質是一個函數。node
getSource(modulePath) { let source = fs.readFileSync(modulePath, 'utf8'); //獲取webpack.config.js中的rules let rules = that.options.module.rules; //遍歷rules調用loader for (let i = 0; i < rules.length; i++) { let rule = rules[i]; // 用rule的test中正則匹配文件的類型是否須要使用laoder if (rule.test.test(modulePath)) { //獲取rule中的loaders,例如['style-laoder','css-loader'] let loaders = rule.use; let length = loaders.length; //loader的數量 let loaderIndex = length - 1; // 往右向左執行 // loader遍歷器 function iterateLoader() { let loaderName = loaders[loaderIndex--]; //loader只是一個包名,須要用require引入 let loader = require(join(that.root, 'node_modules', loaderName)); //使用loader,能夠看出loader的本質是一個函數 source = loader(source); if (loaderIndex >= 0) { iterateLoader(); } } //遍歷執行loader iterateLoader(); break; } } return source; } 複製代碼
因此loader的結構通常爲:jquery
module.exports = function (source) { //TODO須要執行的邏輯 } 複製代碼
import { flatten,concat } from "lodash" console.log(flatten([1,2],[3,4,[5,6]])); console.log(contcat([1,2],[3,4])); 複製代碼
import flatten from "lodash/flatten" import concat from "lodash/concat" console.log(flatten([1,2],[3,4,[5,6]])); console.log(contcat([1,2],[3,4])); 複製代碼
npm install babel-core babel-types -D
複製代碼
//mode_modules/babel-plugin-babel-import let babel = require('babel-core'); let types = require('babel-types'); const visitor = { ImportDeclaration:{ enter(path,state={opts:{}}){ const specifiers = path.node.specifiers; const source = path.node.source; //加載的是lodash而且經過{xxx,xxx}的形式加載 if(state.opts.library == source.value && !types.isImportDefaultSpecifier(specifiers[0])){ const declarations = specifiers.map((specifier,index)=>{ return types.ImportDeclaration( [types.importDefaultSpecifier(specifier.local)], types.stringLiteral(`${source.value}/${specifier.local.name}`) ) }); //替換原來的節點 path.replaceWithMultiple(declarations); } } } } module.exports = function(babel){ return { visitor } } 複製代碼
const path = require("path"); const fs = require("fs"); module.exports = { mode: "development", entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.js$/, use: { loader: "babel-loader", options: { plugins: [["babel-import", { library: "lodash" }]] } } } ] }, resolve: {}, plugins: [], devServer: {} }; 複製代碼
插件向第三方開發者提供了webpack引擎中完整的能力。使用階段式的構建回調,開發者能夠引入自定義插件到webpack構建流程中,幾乎可以任意更改webpack編譯結果。webpack
對象 | 鉤子 |
---|---|
Compiler | run,compile,compilation,make,emit,done |
Compilation | buildModule,normalModuleLoader,succeedModule,finishModule,seal,optimize,after-seal |
Module Factory | beforeResolver,afterResolver,module,parser |
Parser | program,statement,call,expression |
Template Factory | hash,bootstrap,localVars,render |
從前面的文章webpack由淺入深——(webapck簡易版)能夠知道,其實插件是往鉤子中註冊回調的函數。git
//../lib/Compiler class Compiler { constructor(options){ this.options = options; this.hooks = { entryOption: new SyncHook(), afterPlugins: new SyncHook(), run: new SyncHook(), beforeCompile: new SyncHook(), afterCompile: new SyncHook(), emit: new SyncHook(), afterEmit: new SyncHook(), done: new SyncHook(), } } ..... } 複製代碼
#! /usr/bin/env node const path = require('path'); const fs = require('fs'); const root = process.cwd(); const Compiler = require('../lib/Compiler'); let options = require(path.resolve('webpack.config.js')); let compiler = new Compiler(options); compiler.hooks.entryOption.call(); //觸發entryOptions let {plugins} = options; //獲取webpack.config.js中的plugns進行註冊 plugins.forEach(plugin => { plugin.apply(compiler) }); compiler.hooks.afterPlugins.call(), //觸發afterPlugins compiler.run(); 複製代碼
因此簡單插件的格式通常爲:github
class xxxxPlugin{ //new xxxxPlugin(options) constructor(options) { this.options=options; } apply(compiler) { //往鉤子上註冊回調 compiler.hooks.xxxx.tap('xxxxPlugin', ()=> { //TODO執行的邏輯 }); } } module.exports=xxxxPlugin; 複製代碼
前篇webpack由淺入深——(webpack優化配置)中提到了external來cdn引用第三方庫從而減少文件體積,可是存在一個問題,必須手動在模板的html文件中預先寫好script標籤引入第三方的cdn,AutoExternalPlugin實現自動插入script。web
const path = require("path"); const fs = require("fs"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const AutoExternalPlugin = require("./plugin/AutoExternalPlugin"); module.exports = { mode: "development", entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [] }, resolve: {}, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", filename: "index.html" }), new AutoExternalPlugin({ jquery: { varName: "jQuery", url: "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js" } }) ], devServer: { contentBase: path.resolve(__dirname, "dist"), host: "localhost", port: 3000 } }; 複製代碼
/* 1. 分析import xxxx語句是否引用了特定的模塊 2. 自動往html中插入一個script標籤,src就等於cdn地址 3. 生成模塊的時候,若是是插件配置的模塊生成一個外部模塊返回 */ const ExternalModule = require("webpack/lib/ExternalModule"); class AutoExternalPlugin { constructor(options) { this.options = options; //記錄外部模塊 this.externalModules = {}; } apply(compiler) { //normalModuleFactory普通模塊工廠, compiler.hooks.normalModuleFactory.tap('AutoExternalPlugin', (normalModuleFactory) => { normalModuleFactory.hooks.parser .for('javascript/auto') .tap('AutoExternalPlugin', parser => { //當語法拿到會遍歷語法樹,當遍歷到import節點的時候會 //statement就是import $ from 'jquery'語句 //source是'jquery'的文件路徑 ; parser.hooks.import.tap('AutoExternalPlugin', (statement, source) => { //jquery模塊要變成外部模塊 if (this.options[source]) { this.externalModules[source] = true; } }); }) //factory是一個工廠,完成建立模塊的工做 normalModuleFactory.hooks.factory.tap('AutoExternalPlugin', factory => (data, callback) => { const dependency = data.dependencies[0]; let value = dependency.request;//jquery //須要轉成外部模塊,執行這裏的邏輯 if (this.externalModules[value]) { //let $ = window.jQuery; callback(null, new ExternalModule(this.options[value].varName, 'window')); //不然執行正常的工廠方法,默認建立一個普通的模塊 } else { factory(data, callback); } }); }); compiler.hooks.compilation.tap('InlinePlugin', (compilation) => { compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('InlinePlugin', (htmlData, callback) => { Object.keys(this.externalModules).forEach(key => { htmlData.body.unshift({ tagName: 'script', closeTag: true, attributes: { type: 'text/javascript', src: this.options[key].url } }); }); callback(null, htmlData); }); }); } } module.exports = AutoExternalPlugin; 複製代碼
webpack系列文章已經完結,後面會持續增長和修改內容。