Skip to main content

React 脚手架 Typescript 多入口配置

新建项目

npx create-react-app my-app --template typescript

自定义配置

create-react-app 让我们可以快速和专注项目的开发而不用过多的去关心工具和服务的配置,但是有些场景我们还是需要自定配置的,可以执行下面的命令:

npm run eject

Note: 这个操作是不可逆的操作。

如果你还是想让你的项目清新点,可以使用 react-app-rewired

Typescript 项目,需要在 tsconfig.json 中进一步设置,如果直接在 tsconfig.json 文件中直接设置 paths 属性,当重新run 的时候,属性又会被删除。

解决办法:

paths.json

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"svg/*": ["src/svg/*"],
"components/*": ["src/components/*"]
}
}
}

tsconfig.json

{
"extends": "./paths.json"
}

参考: Add baseUrl and paths in tsconfig.json and jsconfig.json #5645

多入口配置

我们知道 webpack 打包最重要的几个参数有 entry, output, plugins,所以我们针对这几个参数进行修改即可。

entry

动态 paths

const path = require('path');
const fs = require('fs');
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
// ...
/**
* @description fetch entry map src/${file}/index
* @example src/window/index,
*/
function fetchEntryMap() {
const dirs = fs.readdirSync(resolveApp('src/'));
const map = {};
dirs.forEach(file => {
const state = fs.statSync(resolveApp(`src/${file}`));
if (state.isDirectory()) {
map[file] = resolveModule(resolveApp, `src/${file}/index`);
}
});
return map;
}

const entryMap = fetchEntryMap();

module.exports = {
// ...
entryMap
}

接下来我们需要魔改下 webpack.config.js中的 entry属性

// ...
/**
* @description setupMultiEntryConfig
* @param {string} webpackEnv
* @param {boolean} shouldUseReactRefresh
*/
function setupMultiEntryConfig(webpackEnv, shouldUseReactRefresh) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';

const entry = {};

Object.keys(paths.entryMap).forEach(key => {
const entryPath = paths.entryMap[key];
// entry[key] = isEnvDevelopment && !shouldUseReactRefresh
// ? [isEnvDevelopment && webpackDevClientEntry, entryPath]
// : entryPath;

entry[key] = [isEnvDevelopment && webpackDevClientEntry, entryPath].filter(Boolean);
});

return { entry };
}

module.exports = function (webpackEnv) {
// ...
const { entry, plugins = [] } = setupMultiEntryConfig(webpackEnv, shouldUseReactRefresh);

return {
entry,
}
}

output

配置打包输出的不同 bundle

module.exports = function (webpackEnv) {
// ...

return {
output: {
filename: isEnvProduction ? 'static/js/[name].js' : isEnvDevelopment && 'static/js/[name].bundle.js',
chunkFilename: isEnvProduction ? 'static/js/[name].chunk.js' : isEnvDevelopment && 'static/js/[name].chunk.js',
// ...
},
// ...
}
}

plugins

webpack.config.js中的方法 setupMultiEntryConfig上增加一些处理,

function setupMultiEntryConfig(webpackEnv, shouldUseReactRefresh) {
// ...
const plugins = [];

Object.keys(paths.entryMap).forEach(key => {
const entryPath = paths.entryMap[key];

// ...

// Generates an `index.html` file with the <script> injected.
const htmlPlugin = new HtmlWebpackPlugin(
Object.assign(
{},
{
chunks: [key],
inject: true,
template: paths.appHtml,
filename: `${key}.html`,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined,
),
);
plugins.push(htmlPlugin);
});

return { entry, plugins };
}

module.exports = function (webpackEnv) {
// ...
const { entry, plugins = [] } = setupMultiEntryConfig(webpackEnv, shouldUseReactRefresh);

return {
// ...
plugins: [
...plugins,
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
// `index.html`
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
// const entrypointFiles = entrypoints.main.filter(
// fileName => !fileName.endsWith('.map')
// );
let entrypointFiles = [];
for (let [entryFile, fileName] of Object.entries(entrypoints)) {
let notMapFiles = fileName.filter(fileName => !fileName.endsWith('.map'));
entrypointFiles = entrypointFiles.concat(notMapFiles);
}

return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
// ...
],
// ...
}
}

start.js

// entry map
const appIndexFiles = Object.keys(paths.entryMap).map(key => paths.entryMap[key]);

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml].concat(appIndexFiles))) {
process.exit(1);
}

build.js

// entry map
const appIndexFiles = Object.keys(paths.entryMap).map(key => paths.entryMap[key]);

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml].concat(appIndexFiles))) {
process.exit(1);
}

参考资料

Get Start

多入口配置