很久以前的前端,没有太多工具化工程化思想,一堆代码塞进去完事儿。如今前端已经很卷,卷到了一个开发环境都够你折腾一宿。那么我们抛开类似nextjs、create-react-app这类的工具或框架,我们该如何从零部署一个属于自己的开发环境呢?这篇文章将讲述如何配置一个基础的脚手架,支持React、TypeScrit和单元测试等必要的功能。在这个基础上,大家能够很方便去个性化脚手架。
很久以前的前端,没有太多工具化工程化思想,一堆代码塞进去完事儿。如今前端已经很卷,卷到了一个开发环境都够你折腾一宿。那么我们抛开类似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安装并调用,或者直接通过 也会报错
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 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/"
},
},
"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/",
"^@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/"
},
"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这类的工具了,底气足了,学习起来是不是更有干劲了?如果文章对你有帮助,也可以持续关注我,不定期更新。