<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Vue3 原始碼閱讀系列,計劃從環境搭建開始,將 Vue3
的響應式模組,執行時模組和編譯器模組,以及狀態庫 Pinia
、路由庫 Vue-Router
的核心原理做一個梳理。這大概是一個漫長的過程。祝自己不要爛尾,祝大家有所收穫。
Pnpm
是新一代的 nodejs
包管理工具。第一個 “P”
意為 Performance
,代表著更佳的效能。
它的主要優點有兩個,一是採用了 hard-link
機制,避免了包的重複安裝,節省了空間,同時能提高專案依賴的安裝速度。二是對monorepo
的支援非常友好,只需要一條設定就能實現。
Monorepo
是一種新的倉庫管理方式。過去的專案,大多采用一個倉庫維護一個專案的方案。對於一個龐大複雜的專案,哪怕只進行一處小小的修改,影響的也是整體。而採用 monorepo
的形式,我們可以在一個倉庫中管理多個包。每個包都可以單獨釋出和使用,就好像是一個倉庫中又有若干個小倉庫。
Vue3 原始碼採用 monorepo 方式進行管理,將眾多模組拆分到 packages 目錄中。
這帶來的最直觀的好處,就是方便管理和維護。而且,它不像 Vue2
那樣將原始碼整體打包對外暴露。Vue3
的這種組織形式,方便的實現了 Tree-shaking
,需要哪個功能就引入對應的模組,能大大減少打包後專案的體積。
首先全域性安裝 pnpm
:
npm install -g pnpm
新建一個目錄並進行初始化:
mkdir vue3-learn cd vue3-learn pnpm init mkdir packages
monorepo
在專案根目錄下新建 pnpm-workspace.yaml
檔案:
packages: - 'packages/*'
意思是,將 packages
目錄下所有的目錄都作為單獨的包進行管理。
通過這樣一個簡單的設定,Monorepo
開發環境搭建好了。
如果大家之前接觸過 lerna + yarn workspace
的方案,就會深有體會,使用 pnpm
的確方便。Vue3
,Element Plus
以前採用的方案就是前者,現在都已經改用後者了。
如果你使用過 Vite
,就一定體驗過它的快。因為 Vite
內建了 esbuild
作為開發階段的構建工具。esbuild
的特點就是快。
Vue3
採用了和 vite
一致的選擇,開發階段使用 esbuild 作為構建工具,在生產階段採用 rollup 進行打包。
我們先安裝一些依賴:
# 原始碼採用 typescript 編寫 pnpm add -D -w typescript # 構建工具,命令列引數解析工具 pnpm add -D -w esbuild rollup rollup-plugin-typescript2 @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-commonjs minimist execa
說明:
-D
:作為開發依賴安裝
-w
:monorepo
環境預設會認為應該將依賴安裝到具體的 package
中。使用 -w 引數,告訴 pnpm 將依賴安裝到 workspace-root,也就是專案的根目錄。
依賴說明:
依賴 | 描述 |
---|---|
typescript | 專案使用 typescript 進行開發 |
esbuild | 開發階段的構建工具 |
rollup | 生產階段的構建工具 |
rollup-plugin-typescript2 | rollup 編譯 ts 的外掛 |
@rollup/plugin-json | rollup 預設採用 esm 方式解析模組,該外掛將 json 解析為 esm 供 rollup 處理 |
@rollup/plugin-node-resolve | rollup 預設採用 esm 方式解析模組,該外掛可以解析安裝在 node_modules 下的第三方模組 |
@rollup/plugin-commonjs | 將 commonjs 模組 轉化為 esm 模組 |
minimist | 解析命令列引數 |
execa | 生產階段開啟子程序 |
pnpm tsc --init
pnpm
的使用基本和 npm
一致。這裡的用法就相當於 npm
中的 npx
:
npx tsc --init
意思是,去 node_modules
下的 .bin
目錄中找到tsc
命令,並執行它。
執行完該命令,會在專案根目錄生成一個 tsconfig.json
檔案,進行一些設定:
{ "compilerOptions": { "outDir": "dist", // 輸出的目錄 "sourceMap": true, // 開啟 sourcemap "target": "es2016", // 轉譯的目標語法 "module": "esnext", // 模組格式 "moduleResolution": "node", // 模組解析方式 "strict": false, // 關閉嚴格模式,就能使用 any 了 "resolveJsonModule": true, // 解析 json 模組 "esModuleInterop": true, // 允許通過 es6 語法引入 commonjs 模組 "jsx": "preserve", // jsx 不跳脫 "lib": ["esnext", "dom"], // 支援的類庫 esnext及dom "baseUrl": ".", // 當前目錄,即專案根目錄作為基礎目錄 "paths": { // 路徑別名設定 "@my-vue/*": ["packages/*/src"] // 當引入 @my-vue/時,去 packages/*/src中找 }, } }
我們先在 packages
目錄下新建兩個模組,分別是 reactivity 響應式模組 和 shared 工具庫模組。然後編寫構建指令碼進行第一次的開發偵錯。
在 packages
下新建 shared
目錄,並初始化:
pnpm init
然後修改 package.json
:
{ "name": "@my-vue/shared", "version": "1.0.0", "description": "@my-vue/shared", "main": "dist/shared.cjs.js", "module": "dist/shared.esm-bundler.js" }
注意 name
欄位的值,我們使用了一個 @scope
作用域,它相當於 npm
包的名稱空間,可以使專案結構更加清晰,也能減少包的重名。
編寫該模組的入口檔案:
// src/index.ts /** * 判斷物件 */ export const isObject = (value) =>{ return typeof value === 'object' && value !== null } /** * 判斷函數 */ export const isFunction= (value) =>{ return typeof value === 'function' } /** * 判斷字串 */ export const isString = (value) => { return typeof value === 'string' } /** * 判斷數位 */ export const isNumber =(value)=>{ return typeof value === 'number' } /** * 判斷陣列 */ export const isArray = Array.isArray
在packages
下新建 reactivity
目錄,並初始化:
pnpm init
然後修改 package.json
:
{ "name": "@my-vue/reactivity", "version": "1.0.0", "description": "@my-vue/reactivity", "main": "dist/reactivity.cjs.js", "module": "dist/reactivity.esm-bundler.js", "buildOptions": { "name": "VueReactivity" } }
在瀏覽器中以 IIFE
格式使用響應式模組時,需要給模組指定一個全域性變數名字,通過 buildOptions.name
進行指定,將來打包時會作為設定使用。
main
指定的檔案支援 commonjs
規範進行匯入,也就是說在nodejs
環境中,通過 require
方法匯入該模組時,會匯入 main
指定的檔案。
同理,module
指定的是使用 ES Module
規範匯入模組時的入口檔案。
編寫該模組的入口檔案:
// src/index.ts import { isObject } from '@my-vue/shared' const obj = {name: 'Vue3'} console.log(isObject(obj))
在 reactivity
包中用到了另一個包 shared
,需要安裝才能使用:
pnpm add @my-vue/shared@workspace --filter @my-vue/reactivity
意思是,將本地 workspace
內的 @my-vue/shared
包,安裝到 @my-vue/reactivity
包中去。
此時,檢視 reactivity
包的依賴資訊:
"dependencies": { "@my-vue/shared": "workspace:^1.0.0" }
在根目錄下新建 scripts
目錄,存放專案構建的指令碼。
新建 dev.js
,作為開發階段的構建指令碼。
// scripts/dev.js // 使用 minimist 解析命令列引數 const args = require('minimist')(process.argv.slice(2)) const path = require('path') // 使用 esbuild 作為構建工具 const { build } = require('esbuild') // 需要打包的模組。預設打包 reactivity 模組 const target = args._[0] || 'reactivity' // 打包的格式。預設為 global,即打包成 IIFE 格式,在瀏覽器中使用 const format = args.f || 'global' // 打包的入口檔案。每個模組的 src/index.ts 作為該模組的入口檔案 const entry = path.resolve(__dirname, `../packages/${target}/src/index.ts`) // 打包檔案的輸出格式 const outputFormat = format.startsWith('global') ? 'iife' : format === 'cjs' ? 'cjs' : 'esm' // 檔案輸出路徑。輸出到模組目錄下的 dist 目錄下,並以各自的模組規範為字尾名作為區分 const outfile = path.resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`) // 讀取模組的 package.json,它包含了一些打包時需要用到的設定資訊 const pkg = require(path.resolve(__dirname, `../packages/${target}/package.json`)) // buildOptions.name 是模組打包為 IIFE 格式時的全域性變數名字 const pgkGlobalName = pkg?.buildOptions?.name console.log('模組資訊:n', entry, 'n', format, 'n', outputFormat, 'n', outfile) // 使用 esbuild 打包 build({ // 打包入口檔案,是一個陣列或者物件 entryPoints: [entry], // 輸入檔案路徑 outfile, // 將依賴的檔案遞迴的打包到一個檔案中,預設不會進行打包 bundle: true, // 開啟 sourceMap sourcemap: true, // 打包檔案的輸出格式,值有三種:iife、cjs 和 esm format: outputFormat, // 如果輸出格式為 IIFE,需要為其指定一個全域性變數名字 globalName: pgkGlobalName, // 預設情況下,esbuild 構建會生成用於瀏覽器的程式碼。如果打包的檔案是在 node 環境執行,需要將平臺設定為node platform: format === 'cjs' ? 'node' : 'browser', // 監聽檔案變化,進行重新構建 watch: { onRebuild (error, result) { if (error) { console.error('build 失敗:', error) } else { console.log('build 成功:', result) } } } }).then(() => { console.log('watching ...') })
使用該指令碼,會使用 esbuild
對 packages
下的包進行構建,打包的結果放到各個包的 dist
目錄下。
在開發階段,我們預設打包成 IIFE
格式,方便在瀏覽器中使用 html
檔案進行測試。在生產階段,會分別打包成 CommonJS
,ES Module
和 IIFE
的格式。
給專案增加一條 scripts
命令:
// package.json "scripts": { "dev": "node scripts/dev.js reactivity -f global" }
意思是,以 IIFE
的格式,打包 reactivity
模組,打包後的檔案可以執行在瀏覽器中。
在終端中執行:
pnpm dev
輸出:
PS D:vue3-learn> pnpm dev
> vue3-learn@1.0.0 dev D:vue3-learn
> node scripts/dev.js reactivity -f global
模組資訊:
D:vue3-learnpackagesreactivitysrcindex.ts
global
iife
D:demo3vue3-learnpackagesreactivitydistreactivity.global.js
watching ...
編寫一個 html
檔案進行測試:
// packages/reactivity/test/index.html <body> <div id="app"></div> <script src="../dist/reactivity.global.js"></script> </body>
開啟瀏覽器控制檯:
到此,一個基本的 monorepo 開發環境就搭建完畢了。
程式碼已上傳至 Github
,點選存取。
更多關於Vue3 pnpm搭建monorepo的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45