微前端 —— portal項目

前言

微前端 —— 理論篇
上一篇介紹了微前端的理念,本片將開始介紹portal項目。css

portal項目介紹

        portal項目包括兩個功能:
        1. 路由分發與應用加載;
        2. 抽離公共依賴; html

目錄結構以下:
圖片描述前端

具體實現

  1. 新建一個文件夾,命名隨便,個人是portal
            執行npm init -y初始化package.json
            如今咱們要作什麼呢?確定是先引入依賴包啊
            webpack打包相關和babel和簡單幾個須要的loader就好了
            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
  2. 在引入所需的依賴以後,開始實現相關功能。先實現公共依賴的引入吧。在上一步咱們沒有引入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.jswebpack.common-deps.config.dev.js


            webpack.common-deps.config.jsgit

    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.jsgithub

    /* 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.jsonscripts中添加命令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)

  3. 公共依賴抽取實現、配置完畢以後,開始利用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)

相關文章
相關標籤/搜索