理解微前端 - 从部署一套自己的前端开发环境(脚手架)开始

很久以前的前端,没有太多工具化工程化思想,一堆代码塞进去完事儿。如今前端已经很卷,卷到了一个开发环境都够你折腾一宿。那么我们抛开类似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.jsonpackage-lock.json 文件,具体怎么操作,相信你使用过Node的话,基本没有啥问题的)

目录如下:

理解微前端 - 从部署一套自己的前端开发环境(脚手架)开始_第1张图片

下面的步骤,尽可能按照顺序来:)

(一)创建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-plugincss-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-pluginpublicPath 设置会让 file-loaderpublicPath 路径失效,导致文件重复提取,可以删除 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 访问,不设置此参数,则会自动定位到 publicdist 文件夹。可以直接使用 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 文件的 eslintConfigjest 部分可以写成:

...
      "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.cssapp.min.js,8080端口将默认运行 public 里的index.html,它将自动定位到dist的文件,所以无需增加dist的二级目录。直接写 app.min.cssapp.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这类的工具了,底气足了,学习起来是不是更有干劲了?如果文章对你有帮助,也可以持续关注我,不定期更新。更好的阅读体验,可查看我的个人博客,感谢阅读:)

你可能感兴趣的:(前端,webpack,前端开发,reactjs,架构)