寫一個(gè)babel插件實(shí)現(xiàn)按需打包的功能

背景:當(dāng)我們同時(shí)引入一個(gè)包中的兩個(gè)方法,有兩種形式

第一種形式

import {flatten,join} from 'lodash';

第二種形式

import flatten from 'lodash/flatten';
import join from 'lodash/join';

對(duì)比兩種形式,我們可以看出:

第一種方式的引入會(huì)把整個(gè)lodash包引進(jìn)來
第二種方式是指引入整個(gè)包中的兩個(gè)方法

顯然我們要用第二種

但是一般的項(xiàng)目中 大部分都是以import解構(gòu)的形式,所以在這里我們就寫一個(gè)插件,當(dāng)我們寫成第一種形式引入的時(shí)候,利用插件轉(zhuǎn)化成第二種形式

這是我們要寫的插件的功能

第一步:初始化一個(gè)webpack的項(xiàng)目

 npm i webpack webpack-cli babel-core babel-loader babel-preset-env babel-preset-stage-0 -D

在Webpack中,提供了mode變量,用于配置運(yùn)行環(huán)境,mode的值可以為development,表示的是開發(fā)模式,或者是production,表示的是生產(chǎn)模式。

在package.json中寫入編譯的命令

"scripts":{
  "build":"webpack --mode production/development"
}

第二步:創(chuàng)建一個(gè)項(xiàng)目結(jié)構(gòu)

webpack-plugin // 項(xiàng)目名稱
    dist // 打包輸出目錄
        bundle.js // 打包輸出的文件
    src // 主要邏輯
        index.js // 項(xiàng)目的入口文件
    ./babelrc // 語法解析配置
    package.json
    webpack.config.js

第三步:寫webpack的配置文件

const path = require('path');
module.exports = {
    entry: './src/index.js',// 入口文件
    output: {
        path: path.join(__dirname, 'dist'), // 輸出路徑
        filename: 'bundle.js' // 輸出的文件名稱
    },
    // 配置
    module: {
        // 配置加載器
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader'
            }
        ]
    }
}

第四步:配置.babelrc

{
  "presets": [
    "env",
    "stage-0"
  ],
  "plugins": [
    [
      // "demand-loading", // 注:這個(gè)是我們自己寫的插件名 顯示先不放,等我們寫好插件后再加上
      {
         "library": "lodash", // 我們?cè)谝媚膫€(gè)庫(kù)的時(shí)候使用我們寫的這個(gè)插件,這里的意思是當(dāng)我們引用lodash庫(kù)的時(shí)候使用我們寫的這個(gè)插件
      },
      "syntax-decorators"
    ]
  ]
}

第五步:index.js中寫入對(duì)比腳本

 // import {flatten,join} from 'lodash'
import flatten from 'lodash/flatten'
import join from 'lodash/join'

先引入后面的這兩句,然后打包一下

Hash: fcb0bd5d9734b5f56676
Version: webpack 4.2.0
Time: 346ms
Built at: 2018-3-27 21:24:33
    Asset      Size  Chunks             Chunk Names
bundle.js  21.3 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 823 bytes {main} [built]
[./src/index.js] 286 bytes {main} [built]
    + 15 hidden modules


看到這樣打包后的代碼,我們發(fā)現(xiàn)這種方式引入 打包后的大小是。21.3k

然后注釋掉后兩行,只引入第一行

    Hash: aa8b689e1072463fc1cd
Version: webpack 4.2.0
Time: 3277ms
Built at: 2018-3-27 21:30:22
    Asset     Size  Chunks             Chunk Names
bundle.js  483 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./node_modules/webpack/buildin/amd-options.js] (webpack)/buildin/amd-options.js 82 bytes {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 823 bytes {main} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 521 bytes {main} [built]
[./src/index.js] 47 bytes {main} [built]
    + 1 hidden module

這次打包后的大小是 483k

通過對(duì)比 證實(shí)了我們所說的兩種引入方式

第六步:咱們來寫寫這個(gè)插件

先對(duì)比一下兩種引入方式的抽象語法樹的差別

image

通過對(duì)比我們發(fā)現(xiàn),只是ImportDeclaration不相同


    const babel = require('babel-core');
    const types = require('babel-types');
    
    let visitor = {
        // 這里的ref是ImportDeclaration的第二個(gè)參數(shù),這里的值是.babelrc中的 {
           // "library": "lodash"
        //}, 這里是指定 我們?cè)谝媚膫€(gè)庫(kù)的時(shí)候使用這個(gè)插件
        ImportDeclaration(path, ref={options:{}}) {
            let node = path.node;
            let specifiers = node.secifiers
            if (options.library == node.soure.value && !types.isImportDeclaration(specifiers[0])) {
                let newImport = specifiers.map((specifier) => (
                    types.importDeclaration([types.ImportDefaultSpecifier(specifier.local)], types.stringLiteral(`${node.soure.value}/${specifier.local.name}`))
                ));
                path.replaceWithMultiple(newImport)
            }
        }     
    }
    
    const code = "import {flatten, join} from 'lodash';";
    
    let r = babel.transform(code, {
        plugins: [
            {visitor}
        ]
    })

在創(chuàng)建替換邏輯的時(shí)候,types上的方法 用github上的這個(gè)網(wǎng)址,哪個(gè)不會(huì)搜哪個(gè),媽媽再也不用擔(dān)心我的學(xué)習(xí)。嘻嘻

第七步:將上面的代碼整理一下放到node_modules文件中

新建一個(gè)文件夾 babel-plugin-demand-loading 放到node_modules中,再新建一個(gè)index.js文件,將下面的代碼放進(jìn)去,再然后進(jìn)入這個(gè)文件夾 npm init -y初始化一個(gè)package.json文件,里面的入口文件寫成index.js

需要注意的事項(xiàng):

第一: babel插件的文件夾命名,必須以 babel-plugin-xxx(你寫的插件名)命名,否則引入不成功
第二: babel插件返回的是一個(gè)對(duì)象,里面有一個(gè)訪問者模式的visitor對(duì)象,里面是我們的轉(zhuǎn)化代碼


    const babel = require('babel-core');
    const types = require('babel-types');
    
    module.exports = {
        visitor:  {
            // 這里的ref是ImportDeclaration的第二個(gè)參數(shù),這里的值是.babelrc中的 {
            // "library": "lodash"
            //}, 這里是指定 我們?cè)谝媚膫€(gè)庫(kù)的時(shí)候使用這個(gè)插件
            ImportDeclaration(path, ref={}) {
                let { opts } = ref
                let node = path.node;
                let specifiers = node.specifiers
                if (opts.library == node.source.value && !types.isImportDeclaration(specifiers[0])) {
                    let newImport = specifiers.map((specifier) => (
                        types.importDeclaration([types.ImportDefaultSpecifier(specifier.local)], types.stringLiteral(`${node.source.value}/${specifier.local.name}`))
                    ));
                    path.replaceWithMultiple(newImport)
                }
            }
        }
    }


最后 npm run build 編譯后,發(fā)現(xiàn)打包后的大小是。20多k說明我們的插件起作用了。

不要厭煩熟悉的事物,每天都進(jìn)步一點(diǎn);不要畏懼陌生的事物,每天都學(xué)習(xí)一點(diǎn);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容