記錄從vuecli打包庫遷移到rollup打包

juan26 發表於 2022-06-08
Vue

前言

最近的專案要實現一個拖拉拽的功能,於是乎需要把原來的模組粒度劃分的更小,需要更多更細的元件,而元件打包對前端來說就是要打包成庫,因為專案是vue專案,之前一直用vuecli來打包(vuecli構建目標(庫)),但是現在要打包很多元件的時候,vuecli的弊端就顯示出來了,打包速度太慢,只有一個入口檔案,沒有依賴其他的包的情況下,打包居然要耗時長達40+s(也可能是本人電腦垃圾的原因),如果是專案裡原來的元件,每個元件打包要耗時120s以上,那麼所有元件總的耗時加起來得要小半個小時了。於是我想提升下打包速度,從vuecli下手是不可能了,那就需要尋求其他的構建工具了,想起vue3的vite,底層用的就是rollup,查了下相關的資料,rollup簡直就是為了打包庫而生的!

準備工作

決定用rollup了,看了下相關文件,開始幹!
踩坑中...

  1. 安裝最新版本的rollup-plugin-vue,打包後渲染不出來報錯vue.openBlock is not a function,顯然是因為vue版本不對,檢視了下外掛資料,發現6.0+以上是支援vue3的,vue2得安裝6.0以下的版本
  2. 因為有個元件sync-tree裡有用到render函式,裡面用到了jsx語法,在打包的時候遇到了報錯如下:

    [!] (plugin commonjs) SyntaxError: Unexpected token (6:12) in F:\work\front-end\study\rollup\src\comp\test.vue?rollup-plugin-vue=script.js
    src/comp/test.vue?rollup-plugin-vue=script.js (6:12)
    4:     render(h){
    5:         return (
    6:             <span class="comp">test-12311</span>
                ^
    7:         )
    8:     }
    SyntaxError: Unexpected token (6:12) in F:\work\front-end\study\rollup\src\comp\test.vue?rollup-plugin-vue=script.js
     at Parser.pp$4.raise (F:\work\front-end\study\rollup\node_modules\rollup\dist\shared\rollup.js:19844:13)
     at Parser.pp$9.unexpected (F:\work\front-end\study\rollup\node_modules\rollup\dist\shared\rollup.js:17138:8)

    報錯程式碼是在rollup-plugin-vue,通過查閱相關的issues發現也有人遇到了和我一樣的問題,但是遺憾的是沒有解決這個問題。通過查詢各種社群的資料,很多人都說只要配置了babel的jsx的preset就可以,配置如下:

    babel({
      presets: ["@vue/babel-preset-jsx"],
    })

    我也這麼配置了但是還是一樣報錯。
    後來找到了一篇文件帶github原始碼示例的rollup打包vue元件庫的文章,clone下來跑了一下果然是可以成功構建的。
    通過比較和篩查發現是因為@rollup/plugin-babel這個包的版本引起的,v5.2.1是可以的,v5.2.2就會報錯了,通過比較原始碼提交日誌發現v5.2.2這個版本修改了filter的方法,新增了stripQuery方法來過濾? 前面的路徑,比如F:\work\front-end\study\rollup\src\comp\test.vue?rollup-plugin-vue=script.js這個路徑之前沒有過濾就是包含.js 的,所以可以編譯成功,但是過濾了? 之後,這個路徑就變成了F:\work\front-end\study\rollup\src\comp\test.vue.js不在編譯範圍內,所以才會編譯報錯。那怎麼解決這個問題呢,只要在babel中配置extensions新增.vue 的配置就可以了,配置如下:

    extensions:['.js', '.jsx', '.es6', '.es', '.mjs','.vue'],

    然後新增了css之後又報錯了

    [!] (plugin babel) SyntaxError: F:\work\front-end\study\rollup\src\my-component.vue?rollup-plugin-vue=styles.0.css: Unexpected token (1:0)
    
    > 1 | .test[data-v-103b65c0] {
     | ^
      2 |   color: red;
      3 | }
      4 | .test .comp[data-v-103b65c0] {
    src/my-component.vue?rollup-plugin-vue=styles.0.css (1:0)
    SyntaxError: F:\work\front-end\study\rollup\src\my-component.vue?rollup-plugin-vue=styles.0.css: Unexpected token (1:0)
    
    > 1 | .test[data-v-103b65c0] {
     | ^
      2 |   color: red;
      3 | }
      4 | .test .comp[data-v-103b65c0] {
     at instantiate (F:\work\front-end\study\rollup\node_modules\@babel\parser\src\parse-error\credentials.js:61:22)
     at toParseError (F:\work\front-end\study\rollup\node_modules\@babel\parser\src\parse-error.js:58:12)

    提示css部分報錯了,原因是我在rollup中的外掛vue中配置了css單獨打包,不打包到js中

     vue({
       css: false, // 把單檔案vue中的樣式,插入到html的style標籤中,
       compileTemplate: true,
     })

    原因和jsx報錯差不多,因為babel加了query的過濾,導致檔案路徑變成了F:\work\front-end\study\rollup\src\my-component.vue,但是css是不需要babel編譯的,所以我們需要排查掉css這部分,通過檢視babel原始碼發現filter部分是由兩部分構成的,一個是extension中的字尾,一部分是自己定義的過濾方法

    const userDefinedFilter = typeof customFilter === 'function' ? customFilter : pluginutils.createFilter(include, exclude);
    filter = id => extensionRegExp.test(stripQuery(id).bareId) && userDefinedFilter(id);

    只要自己寫過濾方法把css排除即可,配置如下:

     filter: id=>{
       console.log(id)
       return /(\.js|\.jsx|\.es6|\.es|\.mjs)$/.test(id)
     }

    附上完整的rollup.config.js檔案內容

    // rollup.config.js
    import resolve from "@rollup/plugin-node-resolve";
    import vue from "rollup-plugin-vue";
    import babel from "@rollup/plugin-babel";
    import commonjs from "@rollup/plugin-commonjs";
    import image from "@rollup/plugin-image";
    import scss from "rollup-plugin-scss";
    import { terser } from "rollup-plugin-terser";
    import CleanCss from "clean-css";
    import alias from "@rollup/plugin-alias";
    import injectProcessEnv from "rollup-plugin-inject-process-env";
    const fs = require("fs");
    const path = require("path");
    const dotenv = require("dotenv");
    const dotenvExpand = require("dotenv-expand");
    
    var myEnv = dotenv.config();
    dotenvExpand.expand(myEnv);
    const myEnvObj = myEnv.parsed;
    const outputName = "myComponents";
    const extensions = [".scss", ".js", ".vue"];
    const customResolver = resolve({
      extensions,
    });
    const config = {
      input: "./src/wrapper.js", // 必須,入口檔案
      output: {
     // 必須,輸出檔案 (如果要輸出多個,可以是一個陣列)
     file: `lib/${outputName}.min.js`,
     format: "umd",
     name: outputName,
     // exports: "named", // 輸出多個檔案
     globals: {
       vue: "Vue", // 告訴rollup全域性變數Vue即是vue
     },
      },
      external: ["vue", "hui", "vuex"], // 外部依賴
      plugins: [
     vue({
       css: false, // 把單檔案vue中的樣式,插入到html的style標籤中,
       compileTemplate: true,
     }),
     resolve({extensions}),
     babel({
       presets: ["@vue/babel-preset-jsx"],
       babelHelpers: 'bundled',
       // exclude: 'node_modules/**',
       extensions:['.js', '.jsx', '.es6', '.es', '.mjs','.vue'],
       filter: id=>{
         console.log(id)
         return /(\.js|\.jsx|\.es6|\.es|\.mjs)$/.test(id) && !/node_modules/.test(id)
       }
     }),
     commonjs(),
     image(),
     scss({
         output: function (styles, styleNodes) {
           const compressed = new CleanCss().minify(styles).styles;
           fs.writeFileSync(
             path.resolve(__dirname, `lib/${outputName}.css`),
             compressed
           );
         },
       }),
     terser({
       keep_fnames: false,
     }), // 壓縮
     alias({
       entries: {
         "@comp": path.resolve(__dirname, "src/comp"),
       },
       customResolver,
     }),
     injectProcessEnv({
       NODE_ENV: "production",
       ...myEnvObj,
     })
      ],
    };
    
    export default config;