微前端 —— 理論篇
上一篇介紹了微前端的理念,本片將開始介紹portal項目。css
portal項目包括兩個功能:
1. 路由分發與應用加載;
2. 抽離公共依賴; html
目錄結構以下:
前端
npm init -y
初始化package.json
npm i webpack webpack-cli webpack-dev-server style-loader css-loader copy-webpack-plugin clean-webpack-plugin babel-loader babel-core @babel/preset-env @babel/plugin-syntax-dynamic-import @babel/core -D
npm i concurrently -D
在引入所需的依賴以後,開始實現相關功能。先實現公共依賴的引入吧。在上一步咱們沒有引入single-spa
的依賴包,是由於single-spa的依賴包是做爲公共依賴導入的。
先建立一個公共依賴的文件夾,這個文件夾在任何地方均可以,由於咱們是經過遠程連接引入的,要是你認爲的single-spa.js文件能夠經過遠程cdn或者其餘連接取到,能夠跳過這一步了
,下面是我建立的公共依賴庫文件夾。
vue
裏面全是個人公共依賴文件node
在portal下新建src文件夾,新建文件index.html
,做爲咱們整個項目的頁面文件。代碼以下react
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title></title> </head> <body> // 此處爲從遠程導入各個項目的js文件 // (react、vue項目打包和運行都會產生一個js文件,此文件就是動態渲染頁面用的) // config.js是portal項目產生的,用來進行路由分發與組件狀態管理等 <script type="systemjs-importmap"> { "imports": { "@portal/config": "http://localhost:8233/config.js", "@portal/menu": "http://localhost:8235/menu.js", "@portal/project1": "http://localhost:8236/project1.js", "@portal/project2": "http://localhost:8237/project2.js" } } </script> <!-- 這裏是引入咱們的systemjs,在公共依賴文件夾中,經過http-server開啓的本地服務,進行遠程訪問--> <script src='http://localhost:8000/systemjs/system.js'></script> <script src='http://localhost:8000/systemjs/amd.js'></script> <script src='http://localhost:8000/systemjs/named-exports.js'></script> <script src='http://localhost:8000/systemjs/use-default.js'></script> <!-- 執行 common-deps.js--> <!-- Load the common deps--> <script src="http://localhost:8234/common-deps.js"></script> <!-- Load the application --> <script> // 執行config.js,進行頁面的動態渲染 System.import('@portal/config') </script> <!-- Static navbar --> <!-- 引入樣式文件 --> <link rel="stylesheet" href="http://localhost:8233/styles.css"> <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> <!-- 菜單容器 --> <div id='menu'></div> <div id='content'> <!-- react應用容器 --> <div id="react"></div> <!-- vue應用容器 --> <div id="vue"></div> </div> </body> </html>
接下來實現common-deps.js
,將咱們全部的公共依賴文件統一引入,並命名webpack
window.SystemJS = window.System function insertNewImportMap(newMapJSON) { const newScript = document.createElement('script') newScript.type = 'systemjs-importmap' newScript.text = JSON.stringify(newMapJSON) const allMaps = document.querySelectorAll('script[type="systemjs-importmap"]') allMaps[allMaps.length - 1].insertAdjacentElement( 'afterEnd', newScript ) } const devDependencies = { imports: { react: 'http://localhost:8000/react.development.js', 'react-dom': 'http://localhost:8000/react-dom.development.js', 'react-dom/server': 'http://localhost:8000/react-dom-server.browser.development.js', 'single-spa': 'http://localhost:8000/single-spa.min.js', lodash: 'http://localhost:8000/lodash.js', rxjs: 'http://localhost:8000/rxjs.umd.js', } } const prodDependencies = { imports: { react: 'http://localhost:8000/react.production.min.js', 'react-dom': 'http://localhost:8000/react-dom.production.min.js', 'react-dom/server': 'http://localhost:8000/react-dom-server.browser.production.min.js', 'single-spa': 'http://localhost:8000/single-spa.min.js', lodash: 'http://localhost:8000/lodash.min.js', rxjs: 'http://localhost:8000/rxjs.umd.min.js', } } const devMode = true // you will need to figure out a way to use a set of production dependencies instead if (devMode) { insertNewImportMap(devDependencies) } else { insertNewImportMap(prodDependencies) }
公共依賴的抽取代碼已經實現了,下面就配置webpack,將這些依賴進行打包,在項目根目錄建立 webpack.common-deps.config.js
、webpack.common-deps.config.dev.js
webpack.common-deps.config.js
git
const path = require('path') const CleanWebpackPlugin = require('clean-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { entry: './src/common-deps.js', output: { filename: 'common-deps.js', path: path.resolve(__dirname, 'build/common-deps'), chunkFilename: '[name].js', }, mode: 'production', node: { fs: 'empty', }, resolve: { modules: [ __dirname, 'node_modules', ], }, devtool: 'sourcemap', plugins: [ new CleanWebpackPlugin(['build/common-deps/']), CopyWebpackPlugin([ {from: path.resolve(__dirname, 'src/common-deps.js')} ]), ], module: { rules: [ {parser: {System: false}}, { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', } } ] } }
webpack.common-deps.config.dev.js
github
/* eslint-env node */ const config = require('./webpack.common-deps.config.js'); const webpack = require('webpack'); config.plugins.push(new webpack.NamedModulesPlugin()); config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.devServer = { headers: { "Access-Control-Allow-Origin": "*", }, } config.mode = 'development' module.exports = config;
上面配置dev-server跨域,很重要!!!不然會由於沒法跨域,訪問不到js文件
最後在package.json
的scripts
中添加命令web
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start:common-deps": "webpack-dev-server --config ./webpack.common-deps.config.dev.js --port 8234", "build:common-deps": "webpack --config ./webpack.common-deps.config.js -p" },
至此,公共依賴的抽取已經完成,執行npm run start:common-deps
,公共依賴就被打包到了portal項目下的build/common-deps/
目錄,經過import也能夠正常導入使用(歸功於system)
公共依賴抽取實現、配置完畢以後,開始利用single-spa
構建咱們的路由分發系統
在src中建立文件activityFns.js
,實現路由的正確匹配,內容以下:
export function prefix(location, ...prefixes) { return prefixes.some( prefix => ( location.href.indexOf(`${location.origin}/${prefix}`) !== -1 ) ) } // return true 則加載 false則不加載 // 這裏的menu是菜單,按理應該一直加載出來的,所以return true export function menu(location) { return true } export function project1(location) { return prefix(location, '', 'page1', 'page2') } export function project2(location) { return prefix(location, 'page3', 'page4') }
在src中建立文件config.js
,經過single-spa
實現路由的註冊
import * as isActive from './activityFns.js' import * as singleSpa from 'single-spa' singleSpa.registerApplication('menu', () => SystemJS.import('@portal/menu'), isActive.menu) singleSpa.registerApplication('project1', () => SystemJS.import('@portal/project1'), isActive.project1) singleSpa.registerApplication('project2', () => SystemJS.import('@portal/project2'), isActive.project2) singleSpa.start()
配置webpack打包,在項目根目錄建立文件webpack.config.config.js
,內容以下:
/* eslint-env node */ const webpack = require('webpack') const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { entry: './src/config.js', output: { filename: 'config.js', library: 'config', libraryTarget: 'amd', path: path.resolve(__dirname, 'build'), }, mode: 'production', module: { rules: [ {parser: {System: false}}, { test: /\.js?$/, exclude: [path.resolve(__dirname, 'node_modules')], loader: 'babel-loader', }, { test: /\.css$/, exclude: [path.resolve(__dirname, 'node_modules'), /\.krem.css$/], use: [ 'style-loader', { loader: 'css-loader', options: { localIdentName: '[path][name]__[local]', }, }, { loader: 'postcss-loader', options: { plugins() { return [ require('autoprefixer') ]; }, }, }, ], }, { test: /\.css$/, include: [path.resolve(__dirname, 'node_modules')], exclude: [/\.krem.css$/], use: ['style-loader', 'css-loader'], }, ], }, resolve: { modules: [ __dirname, 'node_modules', ], }, plugins: [ CopyWebpackPlugin([ {from: path.resolve(__dirname, 'src/index.html')}, {from: path.resolve(__dirname, 'src/styles.css')}, ]), new CleanWebpackPlugin(['build']), ], devtool: 'source-map', externals: [ /^lodash$/, /^single-spa$/, /^rxjs\/?.*$/, ], };
上面的配置中,最後的externals屬性值配置不須要webpack打包的依賴
,由於這些在公共依賴中已經打包好了,不須要單獨打包
在項目根目錄建立文件webpack.config.config.dev.js
,內容以下:
const config = require('./webpack.config.config.js') const webpack = require('webpack') config.plugins.push(new webpack.NamedModulesPlugin()); config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.devServer = { contentBase: './build', historyApiFallback: true, headers: { "Access-Control-Allow-Origin": "*", }, proxy: { "/common/": { target: "http://localhost:8234", pathRewrite: {"^/common" : ""} } } } config.mode = 'development' module.exports = config;
一樣配置跨域
在package.json
中添加命令,最終內容以下
{ "name": "portal", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "concurrently \"npm run start:config\" \"npm run start:common-deps\"", "start:config": "webpack-dev-server --config ./webpack.config.config.dev.js --port 8233", "start:common-deps": "webpack-dev-server --config ./webpack.common-deps.config.dev.js --port 8234", "build": "npm run build:config && npm run build:common-deps", "build:config": "webpack --config ./webpack.config.config.js -p", "build:common-deps": "webpack --config ./webpack.common-deps.config.js -p" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.4.3", "@babel/plugin-syntax-dynamic-import": "7.0.0", "@babel/preset-env": "^7.4.3", "babel-core": "6.26.3", "babel-loader": "8.0.0", "clean-webpack-plugin": "0.1.19", "concurrently": "^4.1.1", "copy-webpack-plugin": "4.5.2", "css-loader": "1.0.0", "style-loader": "0.23.0", "webpack": "4.17.1", "webpack-cli": "3.1.0", "webpack-dev-server": "^3.1.14" } }
至此,整個portal項目就搭建完畢了,下一篇,咱們實現menu項目和project1項目
項目源碼
Portal源碼
微前端 —— 理論篇
微前端 —— menu&&project1(React)
微前端 —— project2項目(VUE)