理解微前端 - 从部署一套自己的前端开发环境(脚手架)开始
很久以前的前端,没有太多工具化工程化思想,一堆代码塞进去完事儿。如今前端已经很卷,卷到了一个开发环境都够你折腾一宿。那么我们抛开类似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安装并调用。
(1)注意:如果要使用npm发布的编译包,设置为下面的属性(如果导出的编译包不是为了使用import,则不要使用下面的配置,否则浏览器会报错):output: { library: { type: 'commonjs' }, },直接使用<script src="global.js" ></script> 也会报错
Uncaught ReferenceError: exports is not defined正确的用法:
<script>const exports = {"__esModule": true};</script> <script src="global.js"></script>(2)并且导出的变量或者函数需要包含 {},如
import __ from './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 react-router-config@5.1.1 react-router-dom@5.2.0 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 mini-css-extract-plugin@2.4.5 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这类的工具了,底气足了,学习起来是不是更有干劲了?如果文章对你有帮助,也可以持续关注我,不定期更新一个小技巧。
本文出自没位道 - Chuckie Chang个人网站,转载请保留出处,谢谢!
文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
文章评论