理解微前端 - 從部署一套自己的前端開發環境(腳手架)開始
很久以前的前端,沒有太多工具化工程化思想,一堆程式碼塞進去完事兒。如今前端已經很卷,捲到了一個開發環境都夠你折騰一宿。那麼我們拋開類似nextjs、create-react-app這類的工具或框架,我們該如何從零部署一個屬於自己的開發環境呢?這篇文章將講述如何配置一個基礎的腳手架,支援React、TypeScrit和單元測試等必要的功能。在這個基礎上,也能夠很方便去個性化腳手架,讓它支援比如Electron、Mobx、Redux、PM2、Express(可用來實現SSR服務端渲染)等你想要的擴充套件功能。
首先,我們要知道對於實際的專案,當然是效率優先,儘可能使用成熟的工具和框架,比如類似Next.js、UmiJS、Ant Design等能夠簡化UI Elements和腳手架環境的東西,都推薦使用它。他們能夠一次性完成常用的UI架構,網站的SEO優化,服務端渲染,資源優化,效能優化(打包,懶載入等),安全優化等。對於一個專案的快速搭建和穩定性是有比較明顯的作用的。這裡我們學會從零部署開發環境,一方面可以利於提升自己擴充套件腳手架的能力,同時也能夠在未來的工具和框架運用中,更加靈活的加入自己的想法。
實質上,我們現在做的事情就是搭建一個比較基礎的“微前端架構(Micro-Frontend Architectures)”,至於更加豐富的功能,可以在這個基礎上不斷豐富。目前某些大廠,都開源了自己的微前端架構工具,相當於也是把整個Web架構集於一體,極大提高了前後端分離、開發協作、部署測試釋出迭代的整合效率,提高生產效率。蘿蔔各有所需,如果你的專案沒有較多的約束性,可以直接使用現成的微前端架構工具,比如Bit、Piral、Modern.js等。當然如果不學習如何搭建自己的腳手架,一旦脫離了別人幫你寫好的框架,你可能會顯得很懵~
微前端架構其實可以很複雜,複雜到一整個大團隊的協作,一個公司的業務體系,甚至不同語言開發者的分散式合作。也可以很簡潔,簡潔到讓他成為自己的生產工具,可以用來開發,除錯,可以用來部署釋出,可以用來協作交流。這次寫這篇文章,其實也是想拋開理論,去從某個角度去理解如何才算是自己的架構?它不一定非要打包釋出,不一定非要變成一個系統的框架,微前端架構,其實和自己的生存環境,工作方式息息相關。它並不是一種標準,相當於是人定義的一種規範,一個體系。用得好了,事半功倍,用不好了,還是有不好的影響的:)
如果要真正搭建一個微前端架構,是非常複雜的,涉及的知識面很廣,但是怎麼去理解它的運作,從一個很小的方面去體驗,就足夠了。真正要使用微前端架構,還是建議引入比較成熟的架構方案,相對於中小型產品,自己瞎折騰下還是可以的。
本文分為兩大部分,一個是基礎配置,一個是深入配置,它將能夠更好地適配你的React專案或者傳統的Web專案(當然我覺得Vue也是同理的思想)。讀完這篇文章,我們將實現一個由淺入深完成一套 Webpack5+TypeScript+Jest+ESLint+SASS+React
的開發環境,也可以把它當做腳手架成品。
必須:在這之前,我們要確保你已經安裝至少Node 10+以上的版本,我自己電腦目前的Node版本是v14.16.0。
(假設你的專案名稱叫my-react-app
, 那麼my-react-app
資料夾內就包含以下的目錄和檔案,你可以通過cd /{your_directory}/my-react-app
命令進入你的目錄,使用 npm 命令安裝和移除依賴項,它也會同時修改package.json
和package-lock.json
檔案,具體怎麼操作,相信你使用過Node的話,基本沒有啥問題的)
目錄如下:
下面的步驟,儘可能按照順序來:)
(一)建立package.json檔案
建立一個package.json檔案,滿足Webpack、TypeScript,Jest,ESLint等基本需求,同時它也滿足基本的JS應用的需求。
Node 專案在專案根目錄中名為 package.json 的檔案中跟蹤依賴關係和元資料。這是專案的核心。它包含名稱、描述和版本之類的資訊,以及執行、開發以及有選擇地將專案釋出到 npm 所需的資訊。如果大家想要詳細瞭解這個檔案的用途,可以參看NPM的官方文件 https://docs.npmjs.com/creating-a-package-json-file
關於某些Dependencies,具體的功能根據自己的需求增加,目前我主要針對我們要配置的這個環境選擇,多餘的依賴就不參與。
下面是已經建立好的示例程式碼(並非最基礎的package.json程式碼),你可以根據需要修改它們。
{ "name": "my-react-app", "version": "0.0.1", "description": "My React App.", "main": "dist/my-react-app.js", "directories": { "test": "test" }, "jest": { "testEnvironment": "jsdom", "transform": { "^.+\\.(js|jsx)$": "babel-jest", "^.+\\.(ts|tsx)?$": "ts-jest" } }, "scripts": { "check": "tsc", "dev": "cross-env NODE_ENV=development webpack --progress --mode development --config build/config.js", "build": "cross-env NODE_ENV=production webpack --progress --mode production --config build/config.js", "test": "cross-env NODE_ENV=test jest" }, "repository": { "type": "git", "url": "my-react-app" }, "keywords": [ "library" ], "license": "MIT", "bugs": { "url": "https://github.com/xizon/my-react-app/issues" }, "homepage": "https://github.com/xizon/my-react-app#readme", "devDependencies": { "@babel/core": "^7.13.14", "@babel/plugin-transform-runtime": "^7.16.4", "@babel/polyfill": "^7.0.0", "@babel/preset-env": "^7.2.0", "@babel/preset-typescript": "^7.1.0", "@types/jest": "^27.0.3", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "babel-loader": "^8.0.4", "babel-plugin-module-resolver": "^4.1.0", "cross-env": "^7.0.3", "eslint": "^7.32.0", "jest": "^27.0.4", "jsdom": "^18.1.1", "moment": "^2.29.1", "terser-webpack-plugin": "^5.1.4", "ts-jest": "^27.0.4", "ts-node": "^10.1.0", "typescript": "^4.3.5", "webpack": "^5.47.1", "webpack-cli": "^4.9.1" }, "eslintConfig": { "parserOptions": { "parser": "@typescript-eslint/parser", "ecmaVersion": 2018, "sourceType": "module" }, "extends": [ "plugin:@typescript-eslint/recommended" ] }, "dependencies": {}, "author": "XXXXXXXXX" }
(二)建立tsconfig.json檔案
tsconfig.json
檔案用來配置TypeScript,具體的配置選項,請閱讀官方文件 https://www.typescriptlang.org/docs/handbook/tsconfig-json.html,下面是我自己的配置
{ "compilerOptions": { "target": "esnext", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "commonjs", "moduleResolution": "node", "isolatedModules": true, "resolveJsonModule": true, "noEmit": true, "sourceMap": true, "declaration": true, "noUnusedLocals": false, "noUnusedParameters": false, "incremental": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": false, "baseUrl": "./src" }, "include": [ "src/**/*.ts" ], "exclude": ["node_modules"] }
(三)建立babel.config.js檔案
babel.config.js
檔案主要用來配置babel,什麼是babel,如何配置它的功能,請參看官方文件 https://babeljs.io/docs/en/config-files
下面是我自己的配置程式碼:
module.exports = { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ], [ "@babel/preset-typescript" ] ], "plugins": [ ["@babel/plugin-transform-runtime", { "regenerator": true } ], ["module-resolver", { "root": ["./src"] }] ] };
(四)建立build/config.js檔案
這個檔案屬於腳手架核心檔案,專案的業務需求、開發功能、資源優化、效能、安全等都在這個檔案中展示(你可以根據需要,使用多個build檔案,這些腳手架檔案將和package.json中的命令配置掛鉤,讓你能夠執行它們)
注意:path.resolve(__dirname, '../dist') 將返回: /Applications/......./dist
檔案的參考程式碼如下(你可以自由擴充套件和改良它,如何改良,你可以參看Webpack的官網文件 https://webpack.js.org/concepts/ ,目前我們使用Webpack 5以上版本,經過自己的實踐,它在效能上要高於4的版本):
'use strict'; const webpack = require('webpack'); const path = require('path'); const json = require('../package.json'); const moment = require('moment'); const TerserPlugin = require("terser-webpack-plugin"); /*! ************************************* * Main configuration ************************************* */ const devMode = process.env.NODE_ENV !== 'production'; const webpackConfig = { devtool: devMode ? 'source-map' : false, mode: devMode === 'development' ? 'development' : 'production', watch: true, resolve: { fallback: { fs: false }, extensions: ['.js', '.ts'], }, entry: { 'app': path.resolve(__dirname, '../src/index.ts'), 'app.min': path.resolve(__dirname, '../src/index.ts') }, output: { library: { name: 'RootLib', type: 'var' }, filename: '[name].js', path: path.resolve(__dirname, '../dist'), }, /* entry: path.resolve(__dirname, '../src/index.ts'), output: { filename: 'app.js', path: path.resolve(__dirname, '../dist'), }, */ optimization: { minimize: true, minimizer: [ new TerserPlugin({ test: /\.min\.js$/i }), ], }, module: { rules: [ { test: /\.(js|ts)$/, loader: 'babel-loader', exclude: path.resolve( __dirname, 'node_modules' ), options: { 'presets': [ '@babel/preset-env', '@babel/preset-typescript' ] } }, ], }, plugins: [] }; // Add souce maps webpackConfig.plugins.push( new webpack.SourceMapDevToolPlugin({ filename: '../dist/[file].map', }) ); // Adds a banner to the top of each generated chunk. webpackConfig.plugins.push( new webpack.BannerPlugin(` My React App @source: https://github.com/xizon @version: ${json.version} (${moment().format( "MMMM D, YYYY" )}) @license: MIT `) ); /*! ************************************* * Exporting webpack module ************************************* */ module.exports = webpackConfig;
有些時候我們的專案會發布到npm,然後直接通過Node安裝並呼叫,或者直接通過 也會報錯
Uncaught ReferenceError: exports is not defined正確的用法:
(2)並且匯出的變數或者函式需要包含 {},如
import __ from './_core/global'; export default __; export { __ };(3)使用可以減小編譯後的js體積
["@babel/preset-env", { "targets": {"esmodules": true} }],
至此,一個基礎的Webpack5+TypeScript+Jest+ESLint的基礎開發環境就已經完成了,接下去,我們還將深入配置React的環境。
(五)深入搭建React環境
我們只是完成了基礎的功能支援,如果我們需要使用React,那麼我們還將進一步配置腳手架。當然,Vue支援也同理。在這個過程中,某些依賴會產生衝突,或者因為TypeScript的某些配置,會造成編譯錯誤,所以我們需要一些測試和檢查,就能發現這些錯誤,並修正一些配置。
這些程式碼都是我經過測試後的基礎參考示例程式碼(大部分已經修正了編譯錯誤),出現JS程式碼編譯錯誤不要緊,我們只要根據終端的報錯,來找出原因,修正腳手架的配置即可。當然,如果是為了方便,你也可以直接使用React官方提供的 create-react-app
來編寫你的應用程式,某些個性化的需求也需要參考文件增加。如果你的專案個性化需求比較多,可以直接創造自己的腳手架,方便混合其它第三方庫和工具。不依賴與第三方的架構。
接下去我們繼續配置React的支援。
第1步: 安裝React相關的依賴項
後面的步驟,如果需要使用npm命令安裝新的依賴,同理進入目錄
先進入你的專案的目錄
cd /{your_directory}/my-react-app
然後執行命令:
sudo npm install
補全TypeScript對於React介面的支援,安裝:
npm install @types/react --save-dev
安裝完基本的依賴,然後安裝react的依賴(這幾個依賴有什麼作用,可以自己去搜索引擎查詢一下,一般是我專案裡常用的一些依賴項)和繼續配置(react入口檔案也可以是ts檔案)
npm install axios react react-dom [email protected] [email protected] npm install @babel/preset-react @babel/plugin-proposal-class-properties --save-dev
安裝新的依賴後,package.json
檔案會自動修改。
第2步: 修改build/config.js
為了適配React,我們需要修改webpack配置檔案 build/config.js
,修改後的程式碼如下(自己可以對比之前的基礎程式碼):
context: __dirname, // to automatically find tsconfig.json resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss', '.sass'], alias: { // specific mappings. // Supports directories and custom aliases for specific files when the express server is running, // you need to configure the following files at the same time: // 1) `babel.config.js` --> "plugins": [["module-resolver", {"alias": {...}} ]] // 2) `tsconfig.json` --> "compilerOptions": { "paths": {...} } // 3) `package.json` --> "jest": { "moduleNameMapper": {...} } '@': path.resolve(__dirname, '../src') } }, entry: { 'app': path.resolve(__dirname, '../src/index.js'), 'app.min': path.resolve(__dirname, '../src/index.js') }, module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, loader: 'babel-loader', exclude: path.resolve(__dirname, '../node_modules' ), options: { 'presets': [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript', { plugins: [ '@babel/plugin-proposal-class-properties' ] } ] } }, ], },
Babel的一些外掛有什麼作用,可以直接去官網多看看文件即可。這裡主要說一下 @babel/plugin-proposal-class-properties
外掛,主要是用來編譯的,可以解決一些靜態類屬性編譯的問題。
第3步: 安裝SASS開發依賴
不論我們使用css-in-js還是外部引用CSS樣式檔案,我們都需要配置SASS外掛,讓其能夠編譯SASS,SCSS檔案,我個人比較喜歡使用SCSS檔案,單獨外部引用,而不是直接css-in-js, 這樣便於我自己維護樣式表。
執行命令
npm install sass-loader node-sass style-loader css-loader [email protected] css-minimizer-webpack-plugin --save-dev
【注意】node-sass(7.0.1)和sass-loader(12.4.0)的版本搭配,無需file-loader就可以提取檔案。會和file-loader衝突,提取的檔案可能無法使用,需要移除file-loader相關配置或者降級到node-sass(4.14.1)和sass-loader(7.1.0)才可搭配file-loader使用
安裝關聯jest單元測試的一個依賴
npm install identity-obj-proxy --save-dev
identity-obj-proxy
外掛使用ES6代理的身份物件, 對模擬CSS模組之類的webpack匯入很有用,它可以解決編譯時匯入CSS的一些錯誤
下面的配置適用於node-sass7.x.x, sass-loader 12.x.x 版本
mini-css-extract-plugin2.5.0版本有bug無法執行
適配CSS檔案,修改webpack檔案 build/config.js
(自己可以對比之前的基礎程式碼)
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); optimization: { minimizer: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: '../dist/[name].css' }), new CssMinimizerPlugin({ test:/\.min\.css$/i, parallel: true, minimizerOptions: { preset: [ "default", { discardComments: { removeAll: true }, }, ], }, }), ], }, module: { rules: [ { test: /\.(sa|sc|c)ss$/, include: [ path.resolve(__dirname, '../src'), // Prevent errors in calling the node library: Module parse failed: Unexpected character'@' path.resolve(__dirname, '../node_modules'), ], use: [ /** * Note: * You can use `style-loader` to inject CSS into the DOM to generate a final js file */ { loader: MiniCssExtractPlugin.loader, //Extracts CSS into separate files ( Step 3 ) options: { // you can specify a publicPath here // by default it use publicPath in webpackOptions.output publicPath: '../dist/' } }, { loader: "css-loader", // interprets @import and url() and will resolve them. ( Step 2 ) options: { sourceMap: true } }, { loader: 'sass-loader', // compiles Sass to CSS ( Step 1 ) options: { sourceMap: true, sassOptions: { /* (nested | expanded | compact | compressed) */ outputStyle: 'expanded' }, } }, ] }, ], },
提示:如果不單獨引用生成的 dist/*.css
檔案,使用js內建的css,刪除 mini-css-extract-plugin
和 css-minimizer-webpack-plugin
相關的配置,然後將樣式loader修改為下面的程式碼(自己可以對比之前的基礎程式碼):
{ test: /\.(sa|sc|c)ss$/, include: [ path.resolve(__dirname, '../src'), // Prevent errors in calling the node library: Module parse failed: Unexpected character'@' path.resolve(__dirname, '../node_modules'), ], use: [ { loader: "style-loader" // creates style nodes from JS strings ( Step 3 ) }, { loader: "css-loader", // interprets @import and url() and will resolve them. //(translates CSS into CommonJS) ( Step 2 ) options: { sourceMap: true } }, { loader: 'sass-loader', // compiles Sass to CSS ( Step 1 ) options: { sourceMap: true, sassOptions: { /* (nested | expanded | compact | compressed) */ outputStyle: 'expanded' }, } }, ] },
第4步: 繼續安裝引用檔案的開發依賴
執行命令:
npm install raw-loader glslify-loader json-loader file-loader --save-dev
注意(1):path.resolve(__dirname, '../dist') 將返回: /Applications/......./dist
注意(2):
mini-css-extract-plugin
的publicPath
設定會讓file-loader
的publicPath
路徑失效,導致檔案重複提取,可以刪除file-loader
外掛的相關配置(不刪除此配置會導致生成圖片解析錯誤)來解決
適配引用的字型,圖片等檔案,修改webpack檔案build/config.js
(自己可以對比之前的基礎程式碼)
module: { rules: [ { test: /\.(glsl|vs|fs|vert|frag)$/, exclude: path.resolve(__dirname, '../node_modules' ), use: [ 'raw-loader', 'glslify-loader' ] }, { test: /\.json$/, use: 'json-loader' }, // Note: // 1) Compatible with node-sass(4+) and sass-loader(7+) // 2) The versions of node-sass (7+) and sass-loader (12+) are matched to extract files without `file-loader` { test: /\.(png|jpe?g|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, loader: 'file-loader', options: { esModule: false, //change the css path via output name (file) { return '[name]_[hash].[ext]' }, outputPath: (url, resourcePath, context) => { //the files from `./src/...` will copy to `./dist/` //original name: path.basename(resourcePath) //fonts if ( resourcePath.indexOf( 'webfonts/' ) >= 0 || resourcePath.indexOf( 'fonts/' ) >= 0 ) { return '../dist/fonts/' + url; } //imags if ( resourcePath.indexOf( 'images/' ) >= 0 || resourcePath.indexOf( 'img/' ) >= 0 ) { return '../dist/mages/' + url; } return '../dist/misc/' + url; }, publicPath: (url, resourcePath, context) => { //the css path of output // If the file is in the root directory, you can leave it empty. If in another directory, // you can write: "/blog". (but no trailing slash) const websiteRootDir = ''; //fonts if ( resourcePath.indexOf( 'webfonts/' ) >= 0 || resourcePath.indexOf( 'fonts/' ) >= 0 ) { return `${websiteRootDir}/dist/fonts/${url}`; } //imags if ( resourcePath.indexOf( 'images/' ) >= 0 || resourcePath.indexOf( 'img/' ) >= 0 ) { return `${websiteRootDir}/dist/images/${url}`; } return `${websiteRootDir}/dist/misc/${url}`; } } } ], },
第5步: 繼續安裝webpack本地開發伺服器開發依賴
執行命令
npm install webpack-dev-server --save-dev
適配4.x.x以上版本的本地服務,修改webpack檔案build/config.js
(自己可以對比之前的基礎程式碼)
設定static
引數後,public/index.html
內的檔案路徑可以寫成如:../dist/app.min.css
, 使用localhost:8080/public/index.html
訪問,不設定此引數,則會自動定位到public
和dist
資料夾。可以直接使用localhost:8080
訪問
程式碼如下:
const WebpackDevServer = require('webpack-dev-server'); /*! ************************************* * Listen the server ************************************* */ const server = new WebpackDevServer(compiler, { // After setting the static parameter, the file path in `public/index.html` can be written as: `../dist/app.min.css` static: path.resolve(__dirname, '../' ), hot: true, // Disables a full-screen overlay in the browser when there are compiler errors or warnings. client: { overlay: { warnings: false, errors: true } }, }); server.listen(8080, "localhost", function (err, result) { if (err) { return console.log(err); } console.log( 'Listening at http://localhost:8080/'); })
第6步: 修改package.json檔案的eslintConfig和jest部分
修改後的程式碼如下(自己可以對比之前的基礎程式碼)
"jest": { "moduleNameMapper": { "\\.(css|less|scss|sass)$": "identity-obj-proxy", "^@/(.*)": "/src/$1" }, }, "eslintConfig": { "parserOptions": { "parser": "@typescript-eslint/parser", "ecmaVersion": 2018, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "extends": [ "plugin:react/recommended", "plugin:@typescript-eslint/recommended" ], "rules": {}, "settings": { "react": { "version": "detect" } } },
別名如果指定更多,package.json
檔案的 eslintConfig
和 jest
部分可以寫成:
... "jest": { "testEnvironment": "jsdom", "moduleNameMapper": { "\\.(css|less|scss|sass)$": "identity-obj-proxy", "^@app.react/config/(.*)": "/src/config/$1", "^@app.react/components/(.*)": "/src/client/components/$1", "^@app.react/router/(.*)": "/src/client/router/$1", "^@app.react/helpers/(.*)": "/src/client/helpers/$1", "^@app.react/services/(.*)": "/src/client/services/$1", "^@app.react/reducers/(.*)": "/src/client/reducers/$1", "^@app.react/pages/(.*)": "/src/client/views/_pages/$1", "^@app.react/actions/(.*)": "/src/client/actions/$1", "^@app.react/server/(.*)": "/src/server/$1", "^@app.react/store/(.*)": "/src/store/$1" }, "transform": { "^.+\\.(js|jsx)$": "babel-jest", "^.+\\.(ts|tsx)?$": "ts-jest" } }, …
第7步: 修改tsconfig.json檔案
特別注意:baseUrl
, "@/*": ["*"]
和 include
,這幾個連帶的設定不能出錯。
參考程式碼如下(自己可以對比之前的基礎程式碼)
{ "compilerOptions": { "jsx": "react", "baseUrl": "./src", "paths": { "@/*": ["*"] } }, "include": [ "src/**/*.ts", "src/**/*.tsx" ], }
別名如果指定更多,tsconfig.json
可以寫成:
... "baseUrl": "./src", "paths": { "@app.react/config/*": ["config/*"], "@app.react/components/*": ["client/components/*"], "@app.react/router/*": ["client/router/*"], "@app.react/helpers/*": ["client/helpers/*"], "@app.react/services/*": ["client/services/*"], "@app.react/reducers/*": ["client/reducers/*"], "@app.react/pages/*": ["client/views/_pages/*"], "@app.react/actions/*": ["client/actions/*"], "@app.react/server/*": ["server/*"], "@app.react/store/*": ["store/*"] } }, …
第8步: 修改babel.config.js檔案
參考程式碼如下(自己可以對比之前的基礎程式碼)
module.exports = { "presets": [ [ "@babel/preset-react" ], ], "plugins": [ [ "@babel/plugin-proposal-class-properties" ], ["module-resolver", { "root": ["./src"], "alias": { "@/": "./src" } }] ] };
別名如果指定更多,babel.config.js
可以寫成:
... ["module-resolver", { "root": ["./src"], "alias": { "@app.react/config": "./src/config", "@app.react/components": "./src/client/components", "@app.react/router": "./src/client/router", "@app.react/helpers": "./src/client/helpers", "@app.react/services": "./src/client/services", "@app.react/reducers": "./src/client/reducers", "@app.react/pages": "./src/client/views/_pages", "@app.react/actions": "./src/client/actions", "@app.react/server": "./src/server", "@app.react/store": "./src/store" } }] …
第9步: 修改webpack的externals屬性【可選】
根據需要修改webpack的排除資料夾,方便建立npm包釋出的編譯檔案。externals
屬性防止將某些 import 的包(package)打包到 bundle 中,而是在執行時(runtime)再去從外部獲取這些擴充套件依賴(external dependencies)。參考程式碼如下:
//Exclude react from bundle externals: [ { // String 'react': 'React', 'react-dom': 'ReactDOM', }, // Function function ({ context, request }, callback) { // Use the same './_all' path to prohibit loading of general style sheets if ( request.indexOf( '@/components/_utils/styles' ) >= 0 ) { return callback(null, 'commonjs ' + './_all'); } if ( request.indexOf( '@/components/_utils/_all' ) >= 0 ) { return callback(null, 'commonjs ' + './_all'); } callback(); }, // Regex /^(jquery|\$)$/i, ],
第10步: 根據需要定義webpack的外掛【可選】
下面的例子定義了一個編譯完成後的外掛,遍歷目錄並且移動刪除。
/*! ************************************* * Run command after webpack build ************************************* */ const glob = require('glob'); const fs = require('fs'); const packagesRoot = glob.sync( path.resolve(__dirname, '../packages/*/*.ts') ); const packagesSub = glob.sync( path.resolve(__dirname, '../packages/*.ts') ); const packages = packagesRoot.concat( packagesSub ); const componentsEntry = {}; packages.map( ( path ) => { const filename = path.split( '/' ).pop().replace('.ts', ''); componentsEntry[ filename ] = path; }); console.log( 'componentsEntry: ', componentsEntry ); class MyPluginCompiledFunction { // Define `apply` as its prototype method which is supplied with compiler as its argument apply(compiler) { // Specify the event hook to attach to compiler.hooks.done.tap('MyPluginCompiledFunction', (compilation) => { //Move the components to folders of root directory const comNames = Object.keys( componentsEntry ); packages.map( ( comPath, index ) => { const newDir = path.resolve(__dirname, `../${comNames[index]}`); const oldPath = path.resolve(__dirname, `../dist/cjs/${comNames[index]}.js`); const newPath = `${newDir}/index.js`; if (!fs.existsSync(newDir)){ fs.mkdirSync(newDir); } fs.rename(oldPath, newPath, function (err) { if (err) throw err console.log(`Successfully ${comNames[index]}.js moved!`); }); }); //remove old folder // Where the recursive option deletes the entire directory recursively. fs.rmdirSync(path.resolve(__dirname, '../dist/cjs'), { recursive: true }); }); } } /*! ************************************* * Main configuration ************************************* */ module.export = { ... plugins: [ new MyPluginCompiledFunction() ] };
第11步: 根據需要定義Node指令碼【可選】
指令碼可以單獨分離出來,拋棄webpack,直接執行,使用node來執行JS檔案即可修改 package.json
檔案:
"scripts": { "clear": "node xxx.js" }
(六)使用webpack內建生成HTML功能
如果建立Electron應用,需要單獨呼叫生成的js,所以內建生成HTML的功能暫時不使用。這裡做一個配置參考。
您可以手動建立 public/index.html
來載入 dist
裡生成的 app.min.css
和 app.min.js
,8080埠將預設執行 public
裡的index.html
,它將自動定位到dist的檔案,所以無需增加dist的二級目錄。直接寫 app.min.css
和 app.min.js
即可
(webpack-dev-server
設定 static
引數後,public/index.html
內的檔案路徑可以寫成如:../dist/app.min.css
, 使用 localhost:8080/public/index.html
訪問)
到目前為止,我們都是在 index.html 檔案中手動引入所有資源,然而隨著應用程式增長,並且一旦開始 在檔名中使用 hash 並輸出 多個 bundle,如果繼續手動管理 index.html 檔案,就會變得困難起來。然而,通過一些外掛可以使這個過程更容易管控。
我們使用webpack的HTML生成功能直接打包生成,並且匯入資源。參看:
https://webpack.docschina.org/guides/asset-management/
https://webpack.docschina.org/guides/output-management/
使用內建的Asset Modules來匯入圖片檔案等,使用 HtmlWebpackPlugin
外掛來生成HTML檔案
(七)結語
好了,我們已經完成了從基礎到深入的基礎腳手架搭建,屬於自己的一個微前端架構算是完成了一個雛形,以其說是雛形,也可以說其實可以直接用於部分適合需求的專案。你也可以再次基礎上增加更多的配置,比如PM2的支援,Express的支援,資源壓縮優化等等功能。
都到這裡了,接下去,我們要做一個專案,我們可以使用自己的腳手架,也可以大膽的使用諸如nextjs或者create-react-app這類的工具了,底氣足了,學習起來是不是更有幹勁了?如果文章對你有幫助,也可以持續關注我,不定期更新。更好的閱讀體驗,可檢視我的個人部落格,感謝閱讀:)