docs-sdk/docs-provider/src/main-plugin.ts (331 lines of code) (raw):

import * as Chain from "webpack-chain"; import { Evnrioment, getEnv } from "@alicloud/console-toolkit-shared-utils"; import * as path from "path"; import VirtualModulesPlugin from "webpack-virtual-modules"; import historyFallback from "connect-history-api-fallback"; import { createProxyMiddleware } from "http-proxy-middleware"; import open from "open"; import { runScript } from "./scripts/runScript"; import { IParams } from "./index"; module.exports = (api: any, opts: IParams, args: any) => { const virtualModules: { [path: string]: string } = {}; const entryListItemCode = [] as string[]; const entryListImportCode = [] as string[]; const demoContainerPath = opts.demoContainerPath || path.resolve(__dirname, "../src2/DemoContainer"); virtualModules[ "/@DemoContainer" ] = `export { default } from '${demoContainerPath}';`; const demoWrapperPath = opts.demoWrapperPath || path.resolve(__dirname, "../src2/DemoWrapper"); virtualModules[ "/@DemoWrapper" ] = `export { default } from '${demoWrapperPath}';`; const demoOptsPath = opts.demoOptsPath || path.resolve(__dirname, "../src2/demoOpts"); virtualModules["/@demoOpts"] = `export { default } from '${demoOptsPath}';`; virtualModules["/@initializer"] = opts.initializerPath ? `import "${opts.initializerPath}"` : ""; virtualModules["/@codesandboxModifier"] = opts.codesandboxModifierPath ? `export { default } from '${opts.codesandboxModifierPath}';` : `export default (files) => files`; if (typeof opts.getDemos === "function") { opts.getDemos().forEach(({ key, path: demoPath, staticMeta = {} }, idx) => { const staticMetaVirtualModulePath = generateVirtualPath( "static-meta", key ); virtualModules[staticMetaVirtualModulePath] = ` import { staticMeta as staticMetaFromFile } from "!!js-file-static-meta-loader!${demoPath}"; const staticMetaFromFindAPI = ${JSON.stringify(staticMeta)}; export const staticMeta = Object.assign({}, staticMetaFromFile, staticMetaFromFindAPI, {_type:"demo"}); `; const virtualModulePath = generateVirtualPath("entry", key); if (virtualModules[virtualModulePath]) { throw new Error(`duplicate key "${key}"`); } virtualModules[virtualModulePath] = ` import * as m from "${demoPath}"; import { imports, code, deps } from "!!demo-info-loader!${demoPath}"; // 避免webpack做编译时分析,发现demoPath里面没有export meta造成warning const { default:demo, meta = {} } = (void 0, m); export { demo, meta, code, imports, deps }; `; entryListImportCode.push(` import { staticMeta as demoStaticMeta${idx} } from "${staticMetaVirtualModulePath}"; `); entryListItemCode.push( `{key: '${key}', staticMeta: demoStaticMeta${idx}, load: () => import('${virtualModulePath}')}` ); }); } if (typeof opts.getMarkdownEntries === "function") { opts .getMarkdownEntries() .forEach(({ key, path: entryPath, staticMeta = {} }, idx) => { const staticMetaVirtualModulePath = generateVirtualPath( "static-meta", key ); virtualModules[staticMetaVirtualModulePath] = ` import { staticMeta as staticMetaFromFile } from "!!md-file-static-meta-loader!${entryPath}"; const staticMetaFromFindAPI = ${JSON.stringify(staticMeta)}; export const staticMeta = Object.assign({}, staticMetaFromFile, staticMetaFromFindAPI, {_type:"md"}); `; entryListImportCode.push(` import { staticMeta as mdStaticMeta${idx} } from "${staticMetaVirtualModulePath}"; `); const virtualModulePath = generateVirtualPath("entry", key); if (virtualModules[virtualModulePath]) { throw new Error(`duplicate key "${key}"`); } virtualModules[virtualModulePath] = ` import markdownSource from "${entryPath}"; import React from "react"; // 在这个模块才引入markdown-renderer,而不是在Loader引入 // 避免所有使用loader的用户都被迫加载markdown-renderer(他可能不需要) import { MarkdownRenderer } from "@alicloud/console-toolkit-markdown-renderer"; export default (props) => { return React.createElement(MarkdownRenderer, Object.assign({ source: markdownSource }, props)); }; `; entryListItemCode.push( `{key: '${key}', staticMeta: mdStaticMeta${idx}, load: () => import('${virtualModulePath}')}` ); }); } if (typeof opts.getTypeInfoEntries === "function") { opts.getTypeInfoEntries().forEach(({ key, path: typePath }, idx) => { const virtualModulePath = generateVirtualPath("entry", key); if (virtualModules[virtualModulePath]) { throw new Error(`duplicate key "${key}"`); } virtualModules[virtualModulePath] = ` import typeInfo from "!!type-info-loader!${typePath}"; export { typeInfo }; `; entryListItemCode.push( `{key: '${key}', staticMeta: {_type:"typeInfo"}, load: () => import('${virtualModulePath}')}` ); }); } if (typeof opts.getNormalEntries === "function") { opts .getNormalEntries() .forEach(({ key, path: entryPath, staticMeta = {} }, idx) => { const staticMetaVirtualModulePath = generateVirtualPath( "static-meta", key ); virtualModules[staticMetaVirtualModulePath] = ` import { staticMeta as staticMetaFromFile } from "!!js-file-static-meta-loader!${entryPath}"; const staticMetaFromFindAPI = ${JSON.stringify(staticMeta)}; export const staticMeta = Object.assign({}, staticMetaFromFile, staticMetaFromFindAPI, {_type:"normal"}); `; entryListImportCode.push(` import { staticMeta as normalStaticMeta${idx} } from "${staticMetaVirtualModulePath}"; `); const virtualModulePath = generateVirtualPath("entry", key); if (virtualModules[virtualModulePath]) { throw new Error(`duplicate key "${key}"`); } virtualModules[virtualModulePath] = ` export * from "${entryPath}"; export { default } from "${entryPath}"; `; entryListItemCode.push( `{key: '${key}', staticMeta: normalStaticMeta${idx}, load: () => import('${virtualModulePath}')}` ); }); } virtualModules["/@entry-list"] = ` ${entryListImportCode.join("\n")} export default [${entryListItemCode.join(",")}];`; // 仅用于本地开发的 id => ServePath 解析逻辑 // 以便本地开发的时候能够从本地加载当前微应用 virtualModules[ "/@resolveAppServePathForLocalDev" ] = `export default undefined;`; // 开发者配置微应用的 id => ServePath 解析逻辑 if (opts.resolveAppServePath) { virtualModules[ "/@resolveAppServePathFromDeveloper" ] = `export {default} from "${opts.resolveAppServePath}";`; } else { virtualModules[ "/@resolveAppServePathFromDeveloper" ] = `export default undefined;`; } const subScriptEnv = { CONSOLEOS_ID: opts.consoleOSId, OUTPUT_PATH: opts.output, // externals参数会经过序列化传递给子进程,所以只能使用支持json化的值 EXTERNALS: JSON.stringify(opts.externals), WEBPACK_CONFIG_PATH: opts.webpackConfigPath, PRESET_OFFICIAL_CONFIG_PATH: opts.presetOfficialConfigPath, }; const isBuild = !getEnv().isDev(); if (isBuild) { api.dispatchSync("registerBeforeBuildStart", async () => { // 构建被微应用external掉的依赖,以便在demo-viewer上加载渲染 runScript("deps-build", { env: subScriptEnv, }); }); } api.on("onChainWebpack", (config: Chain, env: Evnrioment) => { config .entry("index") .clear() .add(path.resolve(__dirname, "../src2/index.tsx")) .end() .context(path.resolve(__dirname, "../src2")) .plugin("demos-module") .use(VirtualModulesPlugin, [virtualModules]) .end() // `/@demos/${key}` 中的模块查找 raw-loader 时, // 按照正常的node_modules查找算法,会直接去找/node_modules然后放弃 // 我们要让webpack能在/@demos虚拟路径中找到loader .resolveLoader.alias.set( "demo-info-loader", path.resolve(__dirname, "./demo-info-loader") ) .set( "js-file-static-meta-loader", path.resolve(__dirname, "./js-file-static-meta-loader") ) .set( "md-file-static-meta-loader", path.resolve(__dirname, "./md-file-static-meta-loader") ) .set("type-info-loader", path.resolve(__dirname, "./type-info-loader")) .end() .end() .module.rule("md") .test(/\.md$/) .rule("raw-loader") .use("raw-loader") .loader(require.resolve("raw-loader")) .end() .end() .end(); config.output.publicPath(""); config.output.path(path.resolve(process.cwd(), opts.output!)); config.externals( (() => { const externals = { react: { root: "React", commonjs2: "react", commonjs: "react", amd: "react", }, "react-dom": { root: "ReactDOM", commonjs2: "react-dom", commonjs: "react-dom", amd: "react-dom", }, "@alicloud/breezr-docs-environment": "@alicloud/breezr-docs-environment", "@breezr-doc-internals/externaled-deps": "@breezr-doc-internals/externaled-deps", }; opts.externals?.forEach((item) => { if (typeof item === "string") { externals[item] = item; } else { externals[item.moduleName] = item.moduleName; } }); return externals; })() ); if (env.isDev()) { config.plugins.delete("openBrowser"); config.devServer.disableHostCheck(true); config.devServer.headers({ "Access-Control-Allow-Origin": "*", }); config.devServer.open(false); config.devServer.hot(false); config.devtool("cheap-source-map"); const port = config.devServer.get("port"); const https = config.devServer.get("https"); const host = config.devServer.get("host"); const servePath = `http${https ? "s" : ""}://${host}:${port}/`; // 这里hostname必须为 127.0.0.1 ,才能避免继承宿主应用的https协议: // https://github.com/webpack/webpack-dev-server/blob/699404b091541242ad3d5f38f1a0022a83ff09b2/client-src/default/utils/createSocketUrl.js#L68 // 示例项目: https://code.alibaba-inc.com/xconsole/open-platform/blob/673749953499316d5c63ce0d727ce7a8597bb51d/packages/xconsole-open-template/config/breezr.docs.config.ts#L23 config.devServer.public(`http${https ? "s" : ""}://127.0.0.1:${port}`); config.devServer.historyApiFallback(false); config.devServer.writeToDisk(true); let hostPort; let depsPort; config.devServer.before(function (app, server, compiler) { // webpack dev server自带的fallback和proxy一起使用时, // 会有bug(多次apply webpack-dev-middleware): // https://github.com/webpack/webpack-dev-server/issues/2716 // 因此我们自己配置fallback和proxy app.use( historyFallback({ index: "/host/index.html", }) ); app.use( "/deps", createProxyMiddleware({ target: "http://localhost:8889", // ws: true, router: (req) => { if (!depsPort) { throw new Error("childProcess is not ready yet"); } return `http://localhost:${depsPort}`; }, }) ); app.use( "/host", createProxyMiddleware({ target: "http://localhost:8889", // ws: true, router: (req) => { if (!hostPort) { throw new Error("childProcess is not ready yet"); } return `http://localhost:${hostPort}`; }, }) ); }); runScript("deps-serve", { env: { ...subScriptEnv, SERVE_PATH: servePath, }, onGetPort: (p) => { depsPort = p; }, }); runScript("host-serve", { env: { ...subScriptEnv, SERVE_PATH: servePath, }, onGetPort: (p) => { hostPort = p; // 用户可以配置开发期间自动打开的url const userConfigOpen = opts.devServer?.open; if (userConfigOpen === false) return; open(typeof userConfigOpen === "string" ? userConfigOpen : servePath); }, }); virtualModules["/@resolveAppServePathForLocalDev"] = ` export default function resolveAppServePath(appId) { if (appId === "${opts.consoleOSId}") { return "${servePath}"; } } `; } if (typeof opts.chainWebpack === "function") { opts.chainWebpack(config, env); } }); }; function generateVirtualPath(category: string, key: string) { if (!["static-meta", "entry"].includes(category)) { throw new Error(`invalid category "${category}"`); } return path.resolve( __dirname, "_virtual_module_", `_${category}_`, key, "virtual.js" ); }