From 54f660c9369b82aa0e1db233623191d61a46b0ea Mon Sep 17 00:00:00 2001 From: Linzp Date: Tue, 11 Aug 2026 16:27:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20production=20=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E5=B0=86=20README.md=20=E6=8B=B7=E5=85=A5=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使 CDN 上 remoteEntry.js 同级可访问文档,供远程组件文档搜索使用。 Co-authored-by: Cursor --- lib/craco-libs-example-plugin.js | 5 +++- lib/craco-readme-plugin.js | 4 +-- lib/readme-webpack-plugin.js | 20 +++++++++++++++ package.json | 2 +- test/readme-webpack-plugin.test.js | 40 ++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 test/readme-webpack-plugin.test.js diff --git a/lib/craco-libs-example-plugin.js b/lib/craco-libs-example-plugin.js index 419100c..f91ad35 100644 --- a/lib/craco-libs-example-plugin.js +++ b/lib/craco-libs-example-plugin.js @@ -18,7 +18,10 @@ module.exports = { description: context.packageJson.description, packageName: context.packageJson.name }]; - }, loaderOptions: { + }, + // 组件库 README 在 example 上一级,需一并打进 build 输出 + readmePath: path.resolve(env.appDir, '../README.md'), + loaderOptions: { description: context.packageJson.description, packageName: context.packageJson.name }, watchTarget: path.resolve(env.appDir, '../doc/**/*'), watchCallback: () => { return { diff --git a/lib/craco-readme-plugin.js b/lib/craco-readme-plugin.js index c19f8bc..82e874e 100644 --- a/lib/craco-readme-plugin.js +++ b/lib/craco-readme-plugin.js @@ -6,7 +6,7 @@ const {stringify} = require("@kne/md-doc"); const ReadmePlugin = { overrideWebpackConfig: ({webpackConfig, context, pluginOptions}) => { - const {loaderOptions, getModuleList} = Object.assign({}, pluginOptions); + const {loaderOptions, getModuleList, readmePath} = Object.assign({}, pluginOptions); webpackConfig.module.rules.push({ test: /README\.md$/, loader: require.resolve('./readme-loader'), options: loaderOptions }); @@ -45,7 +45,7 @@ const ReadmePlugin = { })(); webpackConfig.plugins.push(new ReadmeWebpackPlugin({ - env: context.env, getModuleList + env: context.env, getModuleList, readmePath })); return webpackConfig; diff --git a/lib/readme-webpack-plugin.js b/lib/readme-webpack-plugin.js index 283b7ba..433065c 100644 --- a/lib/readme-webpack-plugin.js +++ b/lib/readme-webpack-plugin.js @@ -1,4 +1,5 @@ const path = require('path'); +const fs = require('fs-extra'); const VirtualModulesPlugin = require('webpack-virtual-modules'); const virtualModules = new VirtualModulesPlugin(); const env = require('./env'); @@ -8,6 +9,16 @@ const buildComponentDocs = require('./build-component-docs'); const ReadmeWebpackPlugin_MODULE_NAMES = 'ReadmeWebpackPlugin_MODULE_NAMES'; +/** 将 README.md 拷到 webpack 输出目录(与 remoteEntry.js 同级),供 CDN/文档搜索访问。 */ +const copyReadmeToOutput = async ({ readmePath, outputPath }) => { + if (!(await fs.pathExists(readmePath))) { + return false; + } + await fs.ensureDir(outputPath); + await fs.copy(readmePath, path.join(outputPath, 'README.md')); + return true; +}; + class ReadmeWebpackPlugin { constructor(options) { this.options = Object.assign({}, options); @@ -40,7 +51,16 @@ class ReadmeWebpackPlugin { }))};`); virtualModules.writeModule('node_modules/readme/index.js', readmes); }); + // production 打包结束后把 README.md 写入输出目录,避免 CDN 上仅有 remoteEntry 而无文档 + compiler.hooks.afterEmit.tapPromise('ReadmeWebpackPlugin', async () => { + if (compiler.options.mode !== 'production') { + return; + } + const readmePath = this.options.readmePath || path.resolve(env.appDir, 'README.md'); + await copyReadmeToOutput({ readmePath, outputPath: compiler.outputPath }); + }); } } module.exports = ReadmeWebpackPlugin; +module.exports.copyReadmeToOutput = copyReadmeToOutput; diff --git a/package.json b/package.json index e951898..d951283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne/modules-dev", - "version": "2.4.7", + "version": "2.4.8", "description": "用于辅助在项目内启动一个规范化组件开发的环境", "publishConfig": { "access": "public", diff --git a/test/readme-webpack-plugin.test.js b/test/readme-webpack-plugin.test.js new file mode 100644 index 0000000..9eb858a --- /dev/null +++ b/test/readme-webpack-plugin.test.js @@ -0,0 +1,40 @@ +const path = require('path'); +const os = require('os'); +const fs = require('fs-extra'); +const { expect } = require('chai'); +const { copyReadmeToOutput } = require('../lib/readme-webpack-plugin'); + +describe('copyReadmeToOutput', () => { + let tempDir; + + beforeEach(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'modules-dev-readme-copy-')); + }); + + afterEach(async () => { + await fs.remove(tempDir); + }); + + it('应将 README.md 拷贝到输出目录(与 remoteEntry 同级)', async () => { + const readmePath = path.join(tempDir, 'README.md'); + const outputPath = path.join(tempDir, 'build'); + await fs.writeFile(readmePath, '# Package docs\n'); + + const ok = await copyReadmeToOutput({ readmePath, outputPath }); + + expect(ok).to.equal(true); + expect(await fs.exists(path.join(outputPath, 'README.md'))).to.equal(true); + expect(await fs.readFile(path.join(outputPath, 'README.md'), 'utf8')).to.equal('# Package docs\n'); + }); + + it('源 README 不存在时返回 false 且不写文件', async () => { + const outputPath = path.join(tempDir, 'build'); + const ok = await copyReadmeToOutput({ + readmePath: path.join(tempDir, 'missing.md'), + outputPath + }); + + expect(ok).to.equal(false); + expect(await fs.exists(path.join(outputPath, 'README.md'))).to.equal(false); + }); +});