导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1. 约定式路由
    • 1.1 初始化项目
    • 1.2 运行项目
      • 1.2.1 pages\index.js
      • 1.2.2 pages\profile.js
      • 1.2.3 pages\user\_layout.js
      • 1.2.4 pages\user\add.js
      • 1.2.5 pages\user\list.js
      • 1.2.6 package.json
      • 1.2.7 启动
  • 2. 调试
    • 2.1 NPM调试
    • 2.1.1 launch.json
    • 2.2 attach调试
      • 2.2.1 package.json
      • 2.2.2 launch.json
  • 3. 运行时
    • 3.1 安装
    • 3.2 .umi2\core\history.js
    • 3.3 .umi2\core\routes.js
    • 3.4 .umi2\umi.js
    • 3.5 src.umi2\index.html
    • 3.6 webpack.config.js
    • 3.7 启动
  • 4. 编译时插件
    • 4.1 bin\umi.js
    • 4.2 lib\cli.js
    • 4.3 lib\PluginAPI.js
    • 4.4 lib\Service.js
    • 4.5 commands\dev.js
    • 4.6 lib\Server.js
    • 4.7 启动服务
  • 5. 生成临时目录
    • 5.1 lib\Service.js
    • 5.2 lib\PluginAPI.js
    • 5.3 lib\cli.js
    • 5.4 webpack.config.js
    • 5.5 lib\plugins\commands\dev.js
    • 5.6 lib\getPaths.js
    • 5.7 lib\utils.js
    • 5.8 lib\writeTmpFile.js
    • 5.9 generateFiles\history.js
    • 5.10 generateFiles\history.tpl
    • 5.11 generateFiles\umi.js
    • 5.12 generateFiles\umi.tpl
    • 5.13 lib\getRoutes.js
    • 5.14 generateFiles\routes.js
    • 5.15 generateFiles\routes.tpl
    • 5.16 启动
  • 6. 启动服务器
    • 6.1 lib\Service.js
    • 6.2 lib\index.html
    • 6.3 lib\webpack.config.js
  • 7. 运行时插件
    • 7.1 src\app.js
    • 7.2 src\foo.js
    • 7.3 src.umi3\core\plugin.js
    • 7.4 src.umi3\core\routes.js
  • 8. 实现运行时插件
    • 8.1 plugin.js
    • 8.2 plugin.tpl
    • 8.3 routes.tpl

1. 约定式路由 #

  • umijs是可扩展的企业级前端应用框架。Umi 以路由为基础的,并以此进行功能扩展。然后配以生命周期完善的插件体系,支持各种功能扩展和业务需求
  • 约定式路由
  • umi

1.1 初始化项目 #

mkdir zhufeng-umi3
npm init -y
cnpm i umi -D

1.2 运行项目 #

1.2.1 pages\index.js #

src\pages\index.js

import React from 'react';
const Index = ()=><div>首页</div>
export default Index

1.2.2 pages\profile.js #

src\pages\profile.js

import React from 'react';
const Profile = ()=><div>个人中心</div>
export default Profile;

1.2.3 pages\user\_layout.js #

src\pages\user\_layout.js

import React from 'react';
const UserLayout = (props)=>(
    <div>
        <ul>
            <li><a href="/user/add">添加用户</a></li>
            <li><a href="/user/list">用户列表</a></li>
        </ul>
        <div>{props.children}</div>
    </div>
)
export default UserLayout;

1.2.4 pages\user\add.js #

src\pages\user\add.js

import React from 'react';
const UserAdd = ()=><div>添加用户</div>
export default UserAdd;

1.2.5 pages\user\list.js #

src\pages\user\list.js

import React from 'react';
const UserList = ()=><div>用户列表</div>
export default UserList;

1.2.6 package.json #

package.json

  "scripts": {
    "dev": "umi dev"
  }

1.2.7 启动 #

npm run dev

2. 调试 #

  • debugging
    • --inspect-brk 指定在第一行就设置断点。也就是说,一开始运行,就是暂停的状态

vscodedebug

2.1 NPM调试 #

2.1.1 launch.json #

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch via NPM",
            "request": "launch",
            "runtimeArgs": [
                "run-script",
                "dev"
            ],
            "runtimeExecutable": "npm",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "type": "pwa-node"
        }
    ]
}

2.2 attach调试 #

2.2.1 package.json #

package.json

  "scripts": {
    "dev": "umi dev",
+   "debug": "node --inspect-brk=9229  ./node_modules/_umi@3.3.1@umi/bin/umi.js dev"
  }

2.2.2 launch.json #

.vscode\launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach to UMI",
            "port": 9230,
            "request": "attach",
            "type": "node"
        }
    ]
}

.vscode\launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach to Remote",
            "port": 9230,
            "request": "attach",
            "type": "node"
        }
    ]
}

3. 运行时 #

3.1 安装 #

cnpm i webpack webpack-cli webpack-dev-server html-webpack-plugin  babel-loader @babel/core @babel/preset-env @babel/preset-react webpack-dev-middleware express mustache cnpm react react-dom react-router-dom -S

3.2 .umi2\core\history.js #

src.umi2\core\history.js

import { createBrowserHistory } from 'history';
let history = createBrowserHistory();
export default history;

3.3 .umi2\core\routes.js #

src.umi2\core\routes.js

export function getRoutes() {
  const routes = [
    {
      "path": "/",
      "exact": true,
      "component": require('@/pages/index.js').default
    },
    {
      "path": "/profile",
      "exact": true,
      "component": require('@/pages/profile.js').default
    },
    {
      "path": "/user",
      "routes": [
        {
          "path": "/user/add",
          "exact": true,
          "component": require('@/pages/user/add.js').default
        },
        {
          "path": "/user/list",
          "exact": true,
          "component": require('@/pages/user/list.js').default
        }
      ],
      "component": require('@/pages/user/_layout.js').default
    }
  ];
  return routes;
}

3.4 .umi2\umi.js #

src.umi2\umi.js

import React from 'react';
import { render } from 'react-dom';
import { Router,Route,Switch } from 'react-router-dom';
import history from './core/history';
import { getRoutes } from './core/routes';
function renderRoutes(routes){
  return routes.map(({path,exact,component:Component,routes:childRoutes=[]},index)=>(
    <Route key={index} path={path} exact={exact} render={
      props=>(
        <Component {...props}>
          <Switch>
            {renderRoutes(childRoutes)}
          </Switch>
        </Component>
      )
    }/>
  ))
}
function renderClient(opts) {
  render(
  <Router history={opts.history}>
   {renderRoutes(opts.routes)}
  </Router>
  ,document.getElementById(opts.rootElement));
}
const opts = {
  routes:getRoutes(),
  history,
  rootElement: 'root'
}
renderClient(opts);

3.5 src.umi2\index.html #

src.umi2\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>umi3</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

3.6 webpack.config.js #

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const cwd = process.cwd();
module.exports = {
  mode: "development",
  entry:'./src/.umi2/umi.js',
  output: {
    path: path.join(__dirname, "dist"),
    filename: "[name].js",
    publicPath:'/'
  },
  devtool: false,
  resolve:{
    alias:{
      '@':path.join(cwd,'src')
    }
  },
  devServer:{
    historyApiFallback:{
      index:'index.html'
    }
  },
  module:{
    rules:[
      {
        test: /\.js$/,
        loader: "babel-loader",
        options: {
          presets:['@babel/preset-env','@babel/preset-react']
        },
        exclude:/node_modules/
      },
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template:'./src/.umi2/index.html'
    })
  ]
};

3.7 启动 #

webpack serve 

4. 编译时插件 #

  • 插件 API
  • Service.ts
  • PluginAPI.ts
  • registerCommand

4.1 bin\umi.js #

bin\umi.js

#!/usr/bin/env node
require('../lib/cli');

4.2 lib\cli.js #

lib\cli.js

let Service = require('./Service');
let dev = require('./plugins/commands/dev');
(async()=>{
    const service = new Service({
        plugins:[{id:'dev',apply:dev}]
    });
    await service.run({name: 'dev'});
})();

4.3 lib\PluginAPI.js #

lib\PluginAPI.js

class PluginAPI {
    constructor(opts) {
        this.id = opts.id;
        this.service = opts.service;
    }
    registerCommand(command) {
      const { name } = command;
      this.service.commands[name] = command;
    }
}
module.exports = PluginAPI;

4.4 lib\Service.js #

lib\Service.js

const PluginAPI = require('./PluginAPI');
class Service {
  constructor(opts) {
    this.commands={};
    this.plugins = opts.plugins;
  }
  async init() {
    for(let plugin of this.plugins){
      const pluginAPI = new PluginAPI({id:plugin.id,service: this });
      plugin.apply(pluginAPI);
    }
  }
  async run(args) {
    this.init();
    return this.runCommand(args);
  }
  async runCommand({name}) {
    const command = this.commands[name];
    const { fn } = command;
    return fn();
  }
}
module.exports = Service;

4.5 commands\dev.js #

lib\plugins\commands\dev.js

let Server = require('../../Server');
const dev = (api) => {
  api.registerCommand({
    name: "dev",
    description: "启动开发服务器",
    fn: async function () {
      new Server().start();
    }
  });
};
module.exports = dev;

4.6 lib\Server.js #

lib\Server.js

let express = require('express');
let http = require('http');
class Service {
    constructor(){
        this.app = express();
    }
    async start() {
        this.listeningApp = http.createServer(this.app);
        this.listeningApp.listen(8000, () => {
            console.log(`http server started at port 8000`);
        })
    }
}
module.exports = Service;

4.7 启动服务 #

node ./bin/umi.js

5. 生成临时目录 #

5.1 lib\Service.js #

lib\Service.js

const PluginAPI = require('./PluginAPI');
+const {AsyncParallelHook} = require('tapable');
class Service {
  constructor(opts) {
    this.commands={};
    this.plugins = opts.plugins;
+    this.hooksByPluginId = {};
+    this.hooks = {};
  }
  async init() {
    for(let plugin of this.plugins){
      const pluginAPI = new PluginAPI({id:plugin.id,service: this });
+      pluginAPI.onGenerateFiles=(fn)=>{
+        pluginAPI.register({pluginId:plugin.id,key:"onGenerateFiles",fn})
+      }
      plugin.apply(pluginAPI);
    }

+    Object.keys(this.hooksByPluginId).forEach((id) => {
+      const hooks = this.hooksByPluginId[id];
+      hooks.forEach((hook) => {
+        const { key } = hook;
+        hook.pluginId = id;
+        this.hooks[key] = (this.hooks[key] || []).concat(hook);
+      });
+    });
  }
+  async applyPlugins(opts) {
+    const hooks = this.hooks[opts.key] || [];
+    const tEvent = new AsyncParallelHook(["_"]);
+    for (const hook of hooks) {
+      tEvent.tapPromise({ name: hook.pluginId },hook.fn);
+    }
+    return await tEvent.promise();
+  }
  async run(args) {
    this.init();
    return this.runCommand(args);
  }
  async runCommand({name}) {
    const command = this.commands[name];
    const { fn } = command;
    return fn();
  }
}
module.exports = Service;

5.2 lib\PluginAPI.js #

lib\PluginAPI.js

class PluginAPI {
    constructor(opts) {
        this.id = opts.id;
        this.service = opts.service;
    }
    registerCommand(command) {
      const { name } = command;
      this.service.commands[name] = command;
    }
+   register(hook) {
+      this.service.hooksByPluginId[this.id] = (
+        this.service.hooksByPluginId[this.id] || []
+      ).concat(hook);
+   }
}
module.exports = PluginAPI;

5.3 lib\cli.js #

lib\cli.js

let Service = require('./Service');
let dev = require('./plugins/commands/dev');
+let history = require('./plugins/generateFiles/history');
+let routes = require('./plugins/generateFiles/routes');
+let umi = require('./plugins/generateFiles/umi');
(async()=>{
    const service = new Service({
        plugins:[
            {id:'dev',apply:dev},
+           {id:'history',apply:history},
+           {id:'routes',apply:routes},
+           {id:'umi',apply:umi}
        ]
    });
    await service.run({name: 'dev'});
})();

5.4 webpack.config.js #

webpack.config.js

module.exports = {
   mode: "development",
+  entry:'./src/.umi3/umi.js'
}

5.5 lib\plugins\commands\dev.js #

lib\plugins\commands\dev.js

let Server = require('../../Server');
const dev = (api) => {
  api.registerCommand({
    name: "dev",
    description: "启动开发服务器",
    fn: async function () {
+      await api.service.applyPlugins({
+        key: 'onGenerateFiles'
+      });
      new Server().start();
    }
  });
};

module.exports = dev;

5.6 lib\getPaths.js #

lib\getPaths.js

const {existsSync,statSync} = require('fs');
const {join} = require('path');
function isDirectoryAndExist(path) {
    return existsSync(path) && statSync(path).isDirectory();
}
let cwd = process.cwd();
let absSrcPath = cwd;
if (isDirectoryAndExist(join(cwd, 'src'))) {
    absSrcPath = join(cwd, 'src');
}
const absPagesPath=join(absSrcPath,'pages');
const tmpDir = '.umi3';
const absTmpPath = join(absSrcPath, tmpDir);
module.exports = {
    absSrcPath,
    absPagesPath,
    tmpDir,
    absTmpPath
}

5.7 lib\utils.js #

lib\utils.js

function winPath(path) {
    return path.replace(/\\/g, "/");
}
exports.winPath = winPath;

5.8 lib\writeTmpFile.js #

lib\writeTmpFile.js

let mkdirp = require('mkdirp');
let {writeFileSync} = require('fs');
let {dirname,join} = require('path');
const {absTmpPath} = require('./getPaths');
function writeTmpFile({path,content}) {
    const absPath = join(absTmpPath, path);
    mkdirp.sync(dirname(absPath));
    writeFileSync(absPath, content, 'utf8');
}
module.exports = writeTmpFile;

5.9 generateFiles\history.js #

lib\plugins\generateFiles\history.js

let { readFileSync } = require("fs");
let { join } = require("path");
let writeTmpFile = require("../../writeTmpFile");
let Mustache = require("mustache");

const plugin = (api) => {
  api.onGenerateFiles(async () => {
    const historyTpl = readFileSync(join(__dirname, "history.tpl"), "utf8");
    writeTmpFile({
      path: "core/history.js",
      content: Mustache.render(historyTpl),
    });
  });
};

module.exports = plugin;

5.10 generateFiles\history.tpl #

lib\plugins\generateFiles\history.tpl

import { createBrowserHistory } from 'history';
let history = createBrowserHistory();
export default history;

5.11 generateFiles\umi.js #

lib\plugins\generateFiles\umi.js

let { readFileSync } = require("fs");
let { join } = require("path");
let writeTmpFile = require("../../writeTmpFile");
let Mustache = require("mustache");

const plugin = (api) => {
  api.onGenerateFiles(async () => {
    const umiTpl = readFileSync(join(__dirname, "umi.tpl"), "utf8");
    writeTmpFile({
      path: "umi.js",
      content: Mustache.render(umiTpl),
    });
  });
};
module.exports = plugin;

5.12 generateFiles\umi.tpl #

lib\plugins\generateFiles\umi.tpl

import React from 'react';
import { render } from 'react-dom';
import { Router,Route,Switch } from 'react-router-dom';
import history from './core/history';
import { getRoutes } from './core/routes';
function renderRoutes(routes){
  return routes.map(({path,exact,component:Component,routes:childRoutes=[]},index)=>(
    <Route key={index} path={path} exact={exact} render={
      props=>(
        <Component {...props}>
          <Switch>
            {renderRoutes(childRoutes)}
          </Switch>
        </Component>
      )
    }/>
  ))
}
function renderClient(opts) {
  render(
  <Router history={opts.history}>
   {renderRoutes(opts.routes)}
  </Router>
  ,document.getElementById(opts.rootElement));
}
const opts = {
  routes:getRoutes(),
  history,
  rootElement: 'root'
}
renderClient(opts);

5.13 lib\getRoutes.js #

lib\getRoutes.js

const { existsSync, readdirSync, readFileSync, statSync } = require("fs");
const { basename,extname,join,relative } = require("path");
const {winPath} = require('./utils');
const {absPagesPath} = require('./getPaths');
function getRoutes(opts={}) {
  const { root, relDir = "" } = opts;
  const files = getFiles(join(root, relDir));
  const routes = files.reduce(fileToRouteReducer.bind(null, opts), []);
  return routes;
}

function fileToRouteReducer(opts, routes, file) {
  const { root, relDir = "" } = opts;
  const absFile = join(root, relDir, file);
  const stats = statSync(absFile);
  if (stats.isDirectory()) {
    const relFile = join(relDir, file);
    const layoutFile = join(root,relFile,'_layout.js');
    routes.push({
        path: normalizePath(relFile),
        routes: getRoutes({
          ...opts,
          relDir: relFile
        }),
        ...(existsSync(layoutFile)?{ component: toComponentPath(root,layoutFile)}:{})
      });
  } else {
    const bName = basename(file, extname(file));
    routes.push(
        {
            path: normalizePath(join(relDir, bName)),
            exact: true,
            component: toComponentPath(root,absFile),
        }
    );
  }
  return routes;
}
const normalizePath = (path)=>{
  path= winPath(path);
  path = `/${path}`;
  path = path.replace(/\/index$/, '/');
  return path;
}
const toComponentPath= (root,absFile)=>{
   return `@/${winPath(relative(join(root, ".."), absFile))}`;
}


function getFiles(root) {
  if (!existsSync(root)) return [];
  return readdirSync(root).filter((file) => {
    if (file.charAt(0) === '_') return false;
    return true;
  });
}

let routes = getRoutes({root:absPagesPath});
module.exports = routes;

5.14 generateFiles\routes.js #

lib\plugins\generateFiles\routes.js

let { readFileSync } = require("fs");
let { join } = require("path");
let writeTmpFile = require("../../writeTmpFile");
let Mustache = require("mustache");
let routes = require('../../getRoutes');
const plugin = (api) => {
  api.onGenerateFiles(async () => {
    const routesTpl = readFileSync(join(__dirname, "routes.tpl"), "utf8");
    writeTmpFile({
      path: "core/routes.js",
      content: Mustache.render(routesTpl, {
        routes: JSON.stringify(routes, replacer, 2).replace(/\"component\": (\"(.+?)\")/g, (global, m1, m2) => {
          return `"component": ${m2.replace(/\^/g, '"')}`;
        }),
      }),
    });
  });
};
function replacer(key, value) {
  switch (key) {
    case "component":
      return `require('${value}').default`;
    default:
      return value;
  }
}
module.exports = plugin;

5.15 generateFiles\routes.tpl #

lib\plugins\generateFiles\routes.tpl

export function getRoutes() {
  const routes = {{{ routes }}};
  return routes;
}

5.16 启动 #

webpack serve 

6. 启动服务器 #

6.1 lib\Service.js #

const express = require('express');
const http = require('http');
+const webpack = require('webpack');
+const {join} = require('path');
+const webpackDevMiddleware = require('webpack-dev-middleware');
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const webpackConfig = require('./webpack.config.js');
+const {absTmpPath,absSrcPath} = require('./getPaths');
class Service {
    constructor(){
        this.app = express();
    }
+    setupDevMiddleware(){
+        webpackConfig.entry = join(absTmpPath, 'umi.js');
+        webpackConfig.resolve.alias['@']=absSrcPath;
+        webpackConfig.plugins.push(new HtmlWebpackPlugin({template:join(__dirname, "index.html")}));
+        const compiler = webpack(webpackConfig);
+        const devMiddleware = webpackDevMiddleware(compiler,{writeToDisk:true});
+        this.app.use(devMiddleware);
+        this.app.use((req,res)=>{
+            res.send(compiler.outputFileSystem.readFileSync(join(__dirname,'dist/index.html'),'utf8'));
+        });
+        return devMiddleware;
+    }
    async start() {
+        const devMiddleware = this.setupDevMiddleware();
+        devMiddleware.waitUntilValid(() => {
+            this.listeningApp = http.createServer(this.app);
+            this.listeningApp.listen(8000, () => {
+                console.log(`http server started at port 8000`);
+            })
+        });
    }
}
module.exports = Service;

6.2 lib\index.html #

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>umi3</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

6.3 lib\webpack.config.js #

lib\webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const cwd = process.cwd();
module.exports = {
  mode: "development",
  output: {
    path: path.join(__dirname, "dist"),
    filename: "[name].js",
    publicPath:'/'
  },
  devtool: false,
  resolve:{
    alias:{}
  },
  devServer:{
    historyApiFallback:{
      index:'index.html'
    }
  },
  module:{
    rules:[
      {
        test: /\.js$/,
        loader: "babel-loader",
        options: {
          presets:['@babel/preset-env','@babel/preset-react']
        },
        exclude:/node_modules/
      },
    ]
  },
  plugins: []
};

7. 运行时插件 #

7.1 src\app.js #

src\app.js

export function patchRoutes({routes}) {
  routes.unshift({
    path: '/foo',
    exact: true,
    component: require('@/foo').default,
  });
}

7.2 src\foo.js #

src\foo.js

import React from 'react';
const Foo = (props) => {
  return (
    <div>Foo</div>
  )
}
export default Foo;

7.3 src.umi3\core\plugin.js #

src.umi3\core\plugin.js

class Plugin {
    constructor(){
        this.hooks = {};
    }
    register(plugin) {
        Object.keys(plugin.apply).forEach((key) => {
            if (!this.hooks[key]) this.hooks[key] = [];
            this.hooks[key] = this.hooks[key].concat(plugin.apply[key]);
        });
    }
    applyPlugins({ key, args }) {
        const hooks = this.hooks[key]  || [];
        hooks.forEach((hook) => {
            hook(args);
        });
    }
}

const plugin = new Plugin();
import * as PluginApp from 'C:/aprepare/zhufeng-umi3-prepare3/src/app.js';
plugin.register({
    apply: PluginApp,
    path: 'C:/aprepare/zhufeng-umi3-prepare3/src/app.js',
});

export { plugin }

7.4 src.umi3\core\routes.js #

src.umi3\core\routes.js

+import { plugin } from './plugin';
export function getRoutes() {
  const routes = [
   {
    "path": "/",
    "exact": true,
    "component": require('@/pages/index.js').default
   },
   {
    "path": "/profile",
    "exact": true,
    "component": require('@/pages/profile.js').default
   }
 ];
+ plugin.applyPlugins({
+    key: 'patchRoutes',
+    args: { routes }
+ });
  return routes;
}

8. 实现运行时插件 #

8.1 plugin.js #

lib\plugins\generateFiles\plugin.js

let { readFileSync,existsSync } = require("fs");
let { join } = require("path");
let writeTmpFile = require("../../writeTmpFile");
let Mustache = require("mustache");
const {winPath} = require('../../utils');
const {absSrcPath} = require('../../getPaths');

const plugin = (api) => {
  api.onGenerateFiles(async () => {
    const pluginTpl = readFileSync(join(__dirname, "plugin.tpl"), "utf8");
    const plugins = [join(absSrcPath,'app.js')];
    writeTmpFile({
      path: "core/plugin.js",
      content: Mustache.render(pluginTpl,{
        plugins: plugins.map((plugin, index) => {
          return {
            index,
            path: winPath(plugin)
          };
        })
      }),
    });
  });
};
module.exports = plugin;

8.2 plugin.tpl #

lib\plugins\generateFiles\plugin.tpl

import { plugin } from './plugin';
class Plugin {
    constructor(){
        this.hooks = {};
    }
    register(plugin) {
        Object.keys(plugin.apply).forEach((key) => {
            if (!this.hooks[key]) this.hooks[key] = [];
            this.hooks[key] = this.hooks[key].concat(plugin.apply[key]);
        });
    }
    applyPlugins({ key, args }) {
        const hooks = this.hooks[key]  || [];
        hooks.forEach((hook) => {
            hook(args);
        });
    }
}

const plugin = new Plugin();
{{#plugins}}
import * as Plugin_{{{ index }}} from '{{{ path }}}';
{{/plugins}}

{{#plugins}}
  plugin.register({
    apply: Plugin_{{{ index }}},
    path: '{{{ path }}}',
  });
{{/plugins}}

export { plugin }

8.3 routes.tpl #

lib\plugins\generateFiles\routes.tpl

+import {plugin} from './plugin';
export function getRoutes() {
  const routes = {{{ routes }}};
+  plugin.applyPlugins({
+    key: 'patchRoutes',
+    args: { routes },
+  });
  return routes;
}

访问验证

请输入访问令牌

Token不正确,请重新输入