craco-plugin-multipage
Version:
React multi page craco plugin
423 lines (384 loc) • 11.5 kB
JavaScript
;
var craco = require('@craco/craco');
var program = require('commander');
var lodash = require('lodash');
var fs = require('fs');
var path = require('path');
var pathToRegexp = require('path-to-regexp');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var program__default = /*#__PURE__*/_interopDefaultLegacy(program);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
const defaultOptions = {
pages: "",
ignore: "",
pageTitle: "React App",
defaultTitle: "React App",
htmlOutputDir: "pages",
HtmlWebpackPluginOptions: {},
};
/**
* 检查用户配置
* 根据插件配置,命令行配置,默认配置生成新的配置,
* @param {Object} paths
* @param {Object} pluginOptions
*/
function getOptions(paths, pluginOptions = {}) {
/* eslint-disable import/no-dynamic-require */
const packages = require(paths.appPackageJson);
checkOptions(pluginOptions);
program__default['default']
.version(packages.version)
.allowUnknownOption(true)
.option("--pages [page1,page2]", "只启动或编译指定的页面")
.option("--ignore [page1,page2]", "忽略指定页面,优先级高于--pages")
.parse(process.argv);
let pages = program__default['default'].pages || "";
if (pluginOptions.pages) {
pages = `${pages},${pluginOptions.pages}`;
}
let ignore = program__default['default'].ignore || "";
if (pluginOptions.ignore) {
ignore = `${ignore},${pluginOptions.ignore}`;
}
return Object.assign({}, defaultOptions, pluginOptions, { pages, ignore });
}
function checkOptions(options) {
if (options.hasOwnProperty("pages") && !lodash.isString(options.pages)) {
console.log(
"pages 必须为string类型,请检查配置!(pages`s type should be a string)"
);
process.exit(1);
}
if (options.hasOwnProperty("ignore") && !lodash.isString(options.ignore)) {
console.log(
"ignore 必须为string类型,请检查配置!(ignore`s type should be a string)"
);
process.exit(1);
}
if (
options.hasOwnProperty("defaultTitle") &&
!lodash.isString(options.defaultTitle)
) {
console.log(
"defaultTitle 必须为string类型,请检查配置!(defaultTitle`s type should be a string)"
);
process.exit(1);
}
if (
options.hasOwnProperty("htmlOutputDir") &&
!lodash.isString(options.htmlOutputDir)
) {
console.log(
"htmlOutputDir 必须为string类型,请检查配置!(htmlOutputDir`s type should be a string)"
);
process.exit(1);
}
if (
options.hasOwnProperty("pageTitle") &&
!lodash.isString(options.pageTitle) &&
!lodash.isObject(options.pageTitle)
) {
console.log(
"pageTitle 必须为{string|object}类型,请检查配置!(pageTitle`s type should be a {string|object})"
);
process.exit(1);
}
if (
options.hasOwnProperty("HtmlWebpackPluginOptions") &&
!lodash.isFunction(options.HtmlWebpackPluginOptions) &&
!lodash.isObject(options.HtmlWebpackPluginOptions)
) {
console.log(
"HtmlWebpackPluginOptions 必须为{function|object}类型,请检查配置!(HtmlWebpackPluginOptions`s type should be a {function|object})"
);
process.exit(1);
}
}
/**
* 移除原有的 HTMLWebpackPlugin和 ManifestPlugin
* @param {object} webpackConfig
*/
function removePlugin(webpackConfig) {
const HtmlWebpackPlugin = {
plugin: {},
config: {},
};
const ManifestPlugin = {
plugin: {},
config: {},
};
webpackConfig.plugins = webpackConfig.plugins.filter((item) => {
if (item.constructor.name === "HtmlWebpackPlugin") {
HtmlWebpackPlugin.plugin = item.constructor;
HtmlWebpackPlugin.config = item.options;
return false;
}
if (item.constructor.name === "ManifestPlugin") {
ManifestPlugin.plugin = item.constructor;
ManifestPlugin.config = item.opts;
return false;
}
return true;
});
return { HtmlWebpackPlugin, ManifestPlugin };
}
let config = null;
let titleMap = null;
/**
* 遍历指定目录
* @param {*} pagePath 遍历的目录
* @param {*} dir
*/
function getPagesInfo(pagePath, dir = "") {
if (dir) {
// 忽略的页面
if (
config.pagesRegexp.ignore.length > 0 &&
config.pagesRegexp.ignore.some(
(item) => !!item.exec(dir.replace(/\\/g, "/"))
)
) {
return [];
}
// 指定的页面
if (
config.pagesRegexp.pages.length > 0 &&
!config.pagesRegexp.pages.some(
(item) => !!item.exec(dir.replace(/\\/g, "/"))
)
) {
return [];
}
}
const fileList = fs__default['default'].readdirSync(pagePath);
const all = [];
const directories = [];
fileList.forEach((fileName) => {
if (!fileName.startsWith(".")) {
all.push(fileName);
if (fs__default['default'].statSync(path__default['default'].resolve(pagePath, fileName)).isDirectory()) {
directories.push(fileName);
}
}
});
// 文件夹中存在文件
if (directories.length !== all.length && dir) {
// 如果 文件夹中存在index.js文件 表示这是个页面
if (fs__default['default'].existsSync(path__default['default'].resolve(config.paths.appSrc, dir, "index.js"))) {
const title = getPageTitle(dir);
return {
name: dir,
dir,
title,
entry: path__default['default'].resolve(config.paths.appSrc, dir, "index.js"),
};
}
return [];
}
// 返回这个文件夹下 所有页面的信息
return lodash.flatten(
directories.map((fileName) => {
const filePath = path__default['default'].resolve(pagePath, fileName);
return getPagesInfo(
filePath,
path__default['default'].relative(config.paths.appSrc, filePath)
);
})
);
}
/**
* 获取页面的title
* @param {string} dir
*/
function getPageTitle(dir) {
if (typeof config.options.pageTitle === "string") {
return config.options.pageTitle;
}
if (typeof config.options.pageTitle === "object") {
const match1 = pathToRegexp.match(dir.replace(/\\/g, "/"), {
decode: decodeURIComponent,
});
if (!titleMap) {
titleMap = Object.entries(config.options.pageTitle);
}
const title = titleMap.find((item) => {
return match1(item[0]);
});
if (title) {
return title[1];
}
}
return config.options.defaultTitle;
}
function getPages(initConfig, pagePath) {
config = initConfig;
return getPagesInfo(pagePath);
}
const pages = [];
/**
* 生成用于匹配指定页面和忽略页面的Regexp
* @param {Object} pluginOptions
*/
function getPagesRegexp(pluginOptions) {
const pages = [];
const ignore = [];
if (pluginOptions.pages) {
pluginOptions.pages.split(",").forEach((item) => {
pages.push(...getPathRegexp(item));
});
}
if (pluginOptions.ignore) {
pluginOptions.ignore.split(",").forEach((item) => {
if (item) {
ignore.push(pathToRegexp.pathToRegexp(item));
}
});
}
return { pages, ignore };
}
/**
* 指定页面的时候 其上级文件夹页面也需要存在
* @param {string} path
*/
function getPathRegexp(path) {
const regexpList = [];
if (path) {
let basePath = "";
path.split("/").forEach((item) => {
let localPath = item;
if (basePath) {
localPath = `${basePath}/${item}`;
}
if (!pages.includes(localPath)) {
regexpList.push(pathToRegexp.pathToRegexp(localPath));
pages.push(localPath);
}
basePath = localPath;
});
}
return regexpList;
}
function getWebpackConfig(
pages = [],
HtmlWebpackPlugin = {},
ManifestPlugin = {},
pluginOptions
) {
const plugins = [];
const entry = {};
const isDev = craco.whenDev(() => true, false);
let customConfig = {};
let isFun = false;
if (lodash.isFunction(pluginOptions.HtmlWebpackPluginOptions)) {
isFun = true;
} else if (lodash.isObject(pluginOptions.HtmlWebpackPluginOptions)) {
customConfig = pluginOptions.HtmlWebpackPluginOptions;
}
pages.forEach((item) => {
entry[item.name] = [
isDev && require.resolve("react-dev-utils/webpackHotDevClient"),
item.entry,
].filter(Boolean);
let config = Object.assign({}, HtmlWebpackPlugin.config, customConfig, {
title: item.title,
chunks: [item.name],
filename: `${pluginOptions.htmlOutputDir}${path.sep}${item.dir}.html`,
});
if (isFun) {
config = pluginOptions.HtmlWebpackPluginOptions(config);
if (!lodash.isObject(config)) {
console.error("HtmlWebpackPluginOptions 应该返回一个 Object");
process.exit(1);
}
}
plugins.push(new HtmlWebpackPlugin.plugin(config));
});
plugins.push(
new ManifestPlugin.plugin(
Object.assign({}, ManifestPlugin.config, {
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = [];
Object.values(entrypoints).forEach((entrypoint) => {
entrypointFiles.push(
...entrypoint.filter((fileName) => !fileName.endsWith(".map"))
);
});
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
})
)
);
return {
plugins,
entry,
};
}
/**
* 生成索引页面
* @param {Object} HtmlWebpackPlugin webpack插件
* @param {Array} pages 页面列表
*/
function getIndexPage(HtmlWebpackPlugin, pages) {
return new HtmlWebpackPlugin.plugin(
Object.assign({}, HtmlWebpackPlugin.config, {
chunks: [],
title: "index/索引页面",
templateContent() {
let html = "<ul>";
pages.forEach((item) => {
if (item.options) {
html += `<li><a href="${item.options.filename}">${item.options.title}(${item.options.filename})</a></li>`;
}
});
return `${html}</ul>`;
},
})
);
}
/* craco-plugin-multipage */
module.exports = {
overrideWebpackConfig: ({
webpackConfig,
cracoConfig,
pluginOptions,
context: { paths },
}) => {
const options = getOptions(paths, pluginOptions);
const pagesRegexp = getPagesRegexp(options);
// 移除原有的 plugin
const { HtmlWebpackPlugin, ManifestPlugin } = removePlugin(webpackConfig);
const pages = getPages({ pagesRegexp, paths, options }, paths.appSrc);
if (pages.length === 0) {
console.error("没有找到任何入口!(Can`t find any entry!)");
process.exit(1);
}
// 生成新的 webpackConfig
const { plugins, entry } = getWebpackConfig(
pages,
HtmlWebpackPlugin,
ManifestPlugin,
options
);
// 修改入口
webpackConfig.entry = entry;
// 添加新的plugin
webpackConfig.plugins.unshift(...plugins);
// creat-react-app 中会检查这个文件是否存在
paths.appIndexJs = pages[0].entry;
craco.whenDev(() => {
webpackConfig.output.filename = "static/js/[name].bundle.js";
// 添加索引页面
webpackConfig.plugins.unshift(getIndexPage(HtmlWebpackPlugin, plugins));
});
// Always return the config object.
return webpackConfig;
},
};