diff --git a/.gitignore b/.gitignore index 9b4fb41..7335a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ example prompts graphify-out +.modules-dev diff --git a/lib/craco-fix-plugin.js b/lib/craco-fix-plugin.js index c1faec8..23210a4 100644 --- a/lib/craco-fix-plugin.js +++ b/lib/craco-fix-plugin.js @@ -1,5 +1,24 @@ const {when} = require("@kne/craco"); const env = require("./env"); + +const allowReadmeOutsideSrc = (webpackConfig) => { + const plugins = webpackConfig.resolve && webpackConfig.resolve.plugins; + if (!Array.isArray(plugins)) { + return; + } + const scopePlugin = plugins.find((plugin) => plugin && plugin.constructor && plugin.constructor.name === 'ModuleScopePlugin'); + if (!scopePlugin) { + return; + } + if (scopePlugin.allowedFiles && typeof scopePlugin.allowedFiles.add === 'function') { + scopePlugin.allowedFiles.add(env.readmeIndexPath); + scopePlugin.allowedFiles.add(env.manifestPath); + } + if (Array.isArray(scopePlugin.allowedPaths) && !scopePlugin.allowedPaths.includes(env.readmeDir)) { + scopePlugin.allowedPaths.push(env.readmeDir); + } +}; + module.exports = { overrideWebpackConfig({webpackConfig, context}) { if (context.env === 'production') { @@ -17,7 +36,11 @@ module.exports = { } }); } - webpackConfig.resolve.fallback = { + webpackConfig.resolve = webpackConfig.resolve || {}; + webpackConfig.resolve.alias = Object.assign({}, webpackConfig.resolve.alias, { + readme: env.readmeDir + }); + webpackConfig.resolve.fallback = Object.assign({}, webpackConfig.resolve.fallback, { "path": false, "util": false, "url": false, @@ -28,7 +51,8 @@ module.exports = { "querystring": false, "zlib": false, "fs": false - }; + }); + allowReadmeOutsideSrc(webpackConfig); return webpackConfig; } }; diff --git a/lib/env.js b/lib/env.js index 3733218..cfc9195 100644 --- a/lib/env.js +++ b/lib/env.js @@ -3,10 +3,15 @@ const ensureSlash = require('@kne/ensure-slash'); const template = require('lodash/template'); const appDir = process.cwd(); -const manifestPath = path.resolve(appDir, 'node_modules/readme/modules.js'); +// 虚拟 readme 不能放在 node_modules 下:workplace 软链 node_modules 时 webpack +// 会 realpath 到 SOURCE,找不到虚拟文件。 +const modulesDevDir = path.resolve(appDir, '.modules-dev'); +const readmeDir = path.resolve(modulesDevDir, 'readme'); +const manifestPath = path.resolve(readmeDir, 'modules.js'); +const readmeIndexPath = path.resolve(readmeDir, 'index.js'); const env = { - appDir, manifestPath + appDir, modulesDevDir, readmeDir, manifestPath, readmeIndexPath }; Object.defineProperties(env, { diff --git a/lib/readme-webpack-plugin.js b/lib/readme-webpack-plugin.js index 755f0b5..e487d56 100644 --- a/lib/readme-webpack-plugin.js +++ b/lib/readme-webpack-plugin.js @@ -9,6 +9,22 @@ const buildComponentDocs = require('./build-component-docs'); const ReadmeWebpackPlugin_MODULE_NAMES = 'ReadmeWebpackPlugin_MODULE_NAMES'; +/** 将虚拟 readme 同步到磁盘(内容不变则不写,避免 watch 循环)。workplace 软链 node_modules 时供 source-map-loader realpath。 */ +const writeReadmeFilesIfChanged = (files) => { + fs.ensureDirSync(env.readmeDir); + Object.entries(files).forEach(([file, content]) => { + let prev = null; + try { + prev = fs.readFileSync(file, 'utf8'); + } catch (e) { + prev = null; + } + if (prev !== content) { + fs.writeFileSync(file, content); + } + }); +}; + /** 将完整聚合文档写入 webpack 输出目录的 README.md(与 remoteEntry.js 同级)。 */ const writeFullReadmeToOutput = async ({ content, outputPath }) => { if (content == null || content === '') { @@ -48,14 +64,24 @@ class ReadmeWebpackPlugin { const moduleNames = compilation.params[ReadmeWebpackPlugin_MODULE_NAMES]; const readmes = `${moduleNames.map(({ name }) => `import ${camelCase(name)} from '${env.moduleAliasName}/${name}/README.md';`).join('\n')}export default {${moduleNames.map(({ name }) => camelCase(name)).join(',')}};`; - virtualModules.writeModule(env.manifestPath, readmes + `\nexport const manifest = ${JSON.stringify(Object.assign({ + const manifestSource = readmes + `\nexport const manifest = ${JSON.stringify(Object.assign({ 'name': env.componentsName, 'version': env.componentsVersion, 'open-version': env.openComponentsVersion, 'public-url': env.publicUrl, 'modules': moduleNames - }))};`); - virtualModules.writeModule('node_modules/readme/index.js', readmes); + }))};`; + virtualModules.writeModule(env.manifestPath, manifestSource); + virtualModules.writeModule(env.readmeIndexPath, readmes); + writeReadmeFilesIfChanged({ + [env.readmeIndexPath]: readmes, + [env.manifestPath]: manifestSource, + [path.join(env.readmeDir, 'package.json')]: JSON.stringify({ + name: 'readme', + main: 'index.js', + private: true + }) + }); }); // production:写入完整聚合 README.md(与根目录 TOC 版 README 无关) compiler.hooks.afterEmit.tapPromise('ReadmeWebpackPlugin', async () => { diff --git a/package.json b/package.json index c2e986d..c8987d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne/modules-dev", - "version": "2.4.10", + "version": "2.4.11", "description": "用于辅助在项目内启动一个规范化组件开发的环境", "publishConfig": { "access": "public", diff --git a/test/craco-fix-plugin.test.js b/test/craco-fix-plugin.test.js index a64c94f..c5e88a7 100644 --- a/test/craco-fix-plugin.test.js +++ b/test/craco-fix-plugin.test.js @@ -1,10 +1,11 @@ const { expect } = require('chai'); const plugin = require('../lib/craco-fix-plugin'); +const env = require('../lib/env'); const createConfig = (concatenateModules = true) => ({ plugins: [], optimization: { concatenateModules }, - resolve: {} + resolve: { alias: {}, plugins: [] } }); describe('craco-fix-plugin concatenateModules', () => { @@ -20,3 +21,12 @@ describe('craco-fix-plugin concatenateModules', () => { expect(webpackConfig.optimization.concatenateModules).to.equal(true); }); }); + +describe('craco-fix-plugin readme alias', () => { + it('应将 readme 别名到 .modules-dev/readme', () => { + const webpackConfig = createConfig(true); + plugin.overrideWebpackConfig({ webpackConfig, context: { env: 'development' } }); + expect(webpackConfig.resolve.alias.readme).to.equal(env.readmeDir); + }); +}); + diff --git a/test/env.test.js b/test/env.test.js new file mode 100644 index 0000000..b13a077 --- /dev/null +++ b/test/env.test.js @@ -0,0 +1,13 @@ +const path = require('path'); +const { expect } = require('chai'); +const env = require('../lib/env'); + +describe('env readme paths', () => { + it('虚拟 readme 应落在 .modules-dev 下而不是 node_modules', () => { + expect(env.readmeDir).to.equal(path.resolve(process.cwd(), '.modules-dev/readme')); + expect(env.readmeIndexPath).to.equal(path.resolve(env.readmeDir, 'index.js')); + expect(env.manifestPath).to.equal(path.resolve(env.readmeDir, 'modules.js')); + expect(env.manifestPath.includes(`${path.sep}node_modules${path.sep}`)).to.equal(false); + expect(env.readmeIndexPath.includes(`${path.sep}node_modules${path.sep}`)).to.equal(false); + }); +});