<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【Vuejs】1241- Vue 是如何用 Rollup 打包的?

          共 6773字,需瀏覽 14分鐘

           ·

          2022-02-22 10:56

          Rollup 是一個 JavaScript 模塊打包器,它將小塊的代碼編譯并合并成更大、更復(fù)雜的代碼,比如打包一個庫或應(yīng)用程序。它使用的是 ES Modules 模塊化標準,而不是之前的模塊化方案,如 CommonJS 和 AMD。ES 模塊可以讓你自由、無縫地使用你最喜愛庫中那些最有用的獨立函數(shù),而讓你的項目無需包含其他未使用的代碼。

          近期在團隊內(nèi)組織學(xué)習(xí) Rollup 專題,在著重介紹了 Rollup 核心概念和插件的 Hooks 機制后,為了讓小伙伴們能夠深入了解 Rollup 在實際項目中的應(yīng)用。我們就把目光轉(zhuǎn)向了優(yōu)秀的開源項目,之后就選擇了尤大的 Vue/Vite/Vue3 項目,接下來本文將先介紹 Rollup 在 Vue 中的應(yīng)用。

          dev 命令

          vue-2.6.14 項目根目錄下的 package.json 文件中,我們可以找到 scripts 字段,在該字段內(nèi)定義了如何構(gòu)建 Vue 項目的相關(guān)腳本。

          {
          ??"name":?"vue",
          ??"version":?"2.6.14",
          ??"sideEffects":?false,
          ??"scripts":?{
          ????"dev":?"rollup?-w?-c?scripts/config.js?--environment?TARGET:web-full-dev",
          ????"dev:cjs":?"rollup?-w?-c?scripts/config.js?--environment?TARGET:web-runtime-cjs-dev",
          ????...
          }

          這里我們以 dev 命令為例,來介紹一下與 rollup 相關(guān)的配置項:

          • -c:指定 rollup 打包的配置文件;
          • -w:開啟監(jiān)聽模式,當文件發(fā)生變化的時候,會自動打包;
          • --environment:設(shè)置環(huán)境變量,設(shè)置后可以通過 process.env 對象來獲取已配置的值。

          dev 命令可知 rollup 的配置文件是 scripts/config.js

          //?scripts/config.js
          //?省略大部分代碼
          if?(process.env.TARGET)?{
          ??module.exports?=?genConfig(process.env.TARGET)
          }?else?{
          ??exports.getBuild?=?genConfig
          ??exports.getAllBuilds?=?()?=>?Object.keys(builds).map(genConfig)
          }

          觀察以上代碼可知,當 process.env.TARGET 有值的話,就會根據(jù) TARGET 的值動態(tài)生成打包配置對象。

          //?scripts/config.js
          function?genConfig?(name)?{
          ??const?opts?=?builds[name]
          ??const?config?=?{
          ????input:?opts.entry,
          ????external:?opts.external,
          ????plugins:?[
          ??????flow(),
          ??????alias(Object.assign({},?aliases,?opts.alias))
          ????].concat(opts.plugins?||?[]),
          ????output:?{
          ??????file:?opts.dest,
          ??????format:?opts.format,
          ??????banner:?opts.banner,
          ??????name:?opts.moduleName?||?'Vue'
          ????},
          ????onwarn:?(msg,?warn)?=>?{
          ??????if?(!/Circular/.test(msg))?{
          ????????warn(msg)
          ??????}
          ????}
          ??}
          ??//?省略部分代碼
          ??return?config
          }

          genConfig 函數(shù)內(nèi)部,會從 builds 對象中獲取當前目標對應(yīng)的構(gòu)建配置對象。當目標為 'web-full-dev' 時,它對應(yīng)的配置對象如下所示:

          //?scripts/config.js
          const?builds?={?
          ??'web-runtime-cjs-dev':?{?...?},
          ??'web-runtime-cjs-prod':?{?...?},
          ??//?Runtime+compiler?development?build?(Browser)
          ??'web-full-dev':?{
          ????entry:?resolve('web/entry-runtime-with-compiler.js'),
          ????dest:?resolve('dist/vue.js'),
          ????format:?'umd',
          ????env:?'development',
          ????alias:?{?he:?'./entity-decoder'?},
          ????banner
          ??},
          }

          在每個構(gòu)建配置對象中,會定義 entry(入口文件)、dest (輸出文件)、format(輸出格式)等信息。當獲取構(gòu)建配置對象后,就根據(jù) rollup 的要求生成對應(yīng)的配置對象。

          需要注意的是,在 Vue 項目的根目錄中是沒有 web 目錄的,該項目的目錄結(jié)構(gòu)如下所示:

          ├──?BACKERS.md
          ├──?LICENSE
          ├──?README.md
          ├──?benchmarks
          ├──?dist
          ├──?examples
          ├──?flow
          ├──?package.json
          ├──?packages
          ├──?scripts
          ├──?src
          ├──?test
          ├──?types
          └──?yarn.lock

          那么 web/entry-runtime-with-compiler.js 入口文件的位置在哪呢?其實是利用了 rollup 的 @rollup/plugin-alias 插件為地址取了個別名。具體的映射規(guī)則被定義在 scripts/alias.js 文件中:

          //?scripts/alias.js
          const?path?=?require('path')
          const?resolve?=?p?=>?path.resolve(__dirname,?'../',?p)

          module.exports?=?{
          ??vue:?resolve('src/platforms/web/entry-runtime-with-compiler'),
          ??compiler:?resolve('src/compiler'),
          ??core:?resolve('src/core'),
          ??shared:?resolve('src/shared'),
          ??web:?resolve('src/platforms/web'),
          ??weex:?resolve('src/platforms/weex'),
          ??server:?resolve('src/server'),
          ??sfc:?resolve('src/sfc')
          }

          根據(jù)以上的映射規(guī)則,我們可以定位到 web 別名對應(yīng)的路徑,該路徑對應(yīng)的文件結(jié)構(gòu)如下:

          ├──?compiler
          ├──?entry-compiler.js
          ├──?entry-runtime-with-compiler.js
          ├──?entry-runtime.js
          ├──?entry-server-basic-renderer.js
          ├──?entry-server-renderer.js
          ├──?runtime
          ├──?server
          └──?util

          到這里結(jié)合前面介紹的 builds 對象,相信你也知道了 Vue 是如何打包不同類型的文件,以滿足不同場景的需求,比如含有編譯器和不包含編譯器的版本。分析完 dev 命令的處理流程,下面我來分析 build 命令。

          build 命令

          同樣,在根目錄下 package.jsonscripts 字段,我們可以找到 build 命令的定義:

          {
          ??"name":?"vue",
          ??"version":?"2.6.14",
          ??"sideEffects":?false,
          ??"scripts":?{
          ????"build":?"node?scripts/build.js",
          ????...
          }

          當你運行 build 命令時,會使用 node 應(yīng)用程序執(zhí)行 scripts/build.js 文件:

          //?scripts/build.js
          let?builds?=?require('./config').getAllBuilds()

          //?filter?builds?via?command?line?arg
          if?(process.argv[2])?{
          ??const?filters?=?process.argv[2].split(',')
          ??builds?=?builds.filter(b?=>?{
          ????return?filters.some(f?=>?b.output.file.indexOf(f)?>?-1?
          ????||?b._name.indexOf(f)?>?-1)
          ??})
          }?else?{
          ??//?filter?out?weex?builds?by?default
          ??builds?=?builds.filter(b?=>?{
          ????return?b.output.file.indexOf('weex')?===?-1
          ??})
          }

          build(builds)

          scripts/build.js 文件中,會先獲取所有的構(gòu)建目標,然后根據(jù)進行過濾操作,最后再調(diào)用 build 函數(shù)進行構(gòu)建操作,該函數(shù)的處理邏輯也很簡單,就是遍歷構(gòu)建列表,然后調(diào)用 buildEntry 函數(shù)執(zhí)行構(gòu)建操作。

          //?scripts/build.js
          function?build?(builds)?{
          ??let?built?=?0
          ??const?total?=?builds.length
          ??const?next?=?()?=>?{
          ????buildEntry(builds[built]).then(()?=>?{
          ??????built++
          ??????if?(built?????????next()
          ??????}
          ????}).catch(logError)
          ??}

          ??next()
          }

          next 函數(shù)執(zhí)行時,就會開始調(diào)用 buildEntry 函數(shù),在該函數(shù)內(nèi)部就是根據(jù)傳入了配置對象調(diào)用 rollup.rollup API 進行構(gòu)建操作:

          //?scripts/build.js
          function?buildEntry?(config)?{
          ??const?output?=?config.output
          ??const?{?file,?banner?}?=?output
          ??const?isProd?=?/(min|prod)\.js$/.test(file)
          ??return?rollup.rollup(config)
          ????.then(bundle?=>?bundle.generate(output))
          ????.then(({?output:?[{?code?}]?})?=>?{
          ??????if?(isProd)?{?//?若為正式環(huán)境,則進行壓縮操作
          ????????const?minified?=?(banner???banner?+?'\n'?:?'')?
          ????+?terser.minify(code,?{
          ??????????toplevel:?true,
          ??????????output:?{
          ????????????ascii_only:?true
          ??????????},
          ??????????compress:?{
          ????????????pure_funcs:?['makeMap']
          ??????????}
          ????????}).code
          ????????return?write(file,?minified,?true)
          ??????}?else?{
          ????????return?write(file,?code)
          ??????}
          ????})
          }

          當打包完成后,下一個環(huán)節(jié)就是生成文件。在 buildEntry 函數(shù)中是通過調(diào)用 write 函數(shù)來生成文件:

          //?scripts/build.js
          const?fs?=?require('fs')

          function?write?(dest,?code,?zip)?{
          ??return?new?Promise((resolve,?reject)?=>?{
          ????function?report?(extra)?{
          ??????console.log(blue(path.relative(process.cwd(),?dest))?
          ?????+?'?'?+?getSize(code)?+?(extra?||?''))
          ??????resolve()
          ????}

          ????fs.writeFile(dest,?code,?err?=>?{
          ??????if?(err)?return?reject(err)
          ??????if?(zip)?{
          ????????zlib.gzip(code,?(err,?zipped)?=>?{
          ??????????if?(err)?return?reject(err)
          ??????????report('?(gzipped:?'?+?getSize(zipped)?+?')')
          ????????})
          ??????}?else?{
          ????????report()
          ??????}
          ????})
          ??})
          }

          write 函數(shù)內(nèi)部是通過 fs.writeFile 函數(shù)來生成文件,該函數(shù)還支持 zip 參數(shù),用于輸出經(jīng)過 gzip 壓縮后的大小?,F(xiàn)在我們已經(jīng)分析完了 devbuild 命令,最后我們來簡單介紹一下構(gòu)建過程中所使用的一些核心插件。

          rollup 插件

          package.json ?文件中,我們可以看到 Vue2 項目中用到的 rollup 插件:

          //?package.json
          {
          ??"name":?"vue",
          ??"version":?"2.6.14",
          ??"devDependencies":?{
          ????"rollup-plugin-alias":?"^1.3.1",
          ????"rollup-plugin-buble":?"^0.19.6",
          ????"rollup-plugin-commonjs":?"^9.2.0",
          ????"rollup-plugin-flow-no-whitespace":?"^1.0.0",
          ????"rollup-plugin-node-resolve":?"^4.0.0",
          ????"rollup-plugin-replace":?"^2.0.0",
          ??}
          }

          其中,"rollup-plugin-alias" 插件在前面我們已經(jīng)知道它的作用了。而其他插件的作用如下:

          • rollup-plugin-buble:該插件使用 buble 轉(zhuǎn)換 ES2015 代碼,它已經(jīng)被移到新的倉庫 @rollup/plugin-buble;
          • rollup-plugin-commonjs:該插件用于把 CommonJS 模塊轉(zhuǎn)換為 ES6 Modules,它已經(jīng)移到新的倉庫 @rollup/plugin-commonjs;
          • rollup-plugin-flow-no-whitespace:該插件用于移除 flow types 中的空格;
          • rollup-plugin-node-resolve:該插件用于支持使用 node_modules 中第三方模塊,會使用 Node 模塊解析算法來定位模塊。它也被移動到新的倉庫 @rollup/plugin-node-resolve;
          • rollup-plugin-replace:該插件用于在打包時執(zhí)行字符串替換操作,它也被移動到新的倉庫 @rollup/plugin-replace。

          除了以上的插件,在實際的項目中,你也可以使用 Rollup 官方倉庫提供的插件,來實現(xiàn)對應(yīng)的功能,具體如下圖所示(僅包含部分插件):

          (來源:https://github.com/rollup/plugins)

          總結(jié)

          本文只是簡單介紹了 Rollup 在 Vue 2 中的應(yīng)用,很多細節(jié)并沒有展開介紹,感興趣的小伙伴可以自行學(xué)習(xí)一下。如果遇到問題的話,歡迎跟我一起交流哈。另外,你們也可以自行分析一下在 Vue 3 和 Vite 項目中是如何利用 Rollup 進行打包的。

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章


          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  影音先锋AV麻豆啪啪资源网 | 青青草黄视频无限在线观 | 欧美肏逼网站 | 91亚洲国产成人久久精品网站 | 学生妹内射视频 |