From 970f6d6e4778bf76929b5005288c099e23057d37 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 29 Dec 2021 19:16:29 +0800 Subject: [PATCH 01/20] chore: version --- packages/icejs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/icejs/package.json b/packages/icejs/package.json index aeecf30f3b..c5efd587a4 100644 --- a/packages/icejs/package.json +++ b/packages/icejs/package.json @@ -1,6 +1,6 @@ { "name": "ice.js", - "version": "2.4.2", + "version": "2.5.0", "description": "command line interface and builtin plugin for icejs", "author": "ice-admin@alibaba-inc.com", "homepage": "", From 7973ef1dc4b8763da7fdca6330e5bbf98db0a5d3 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 6 Jan 2022 15:55:06 +0800 Subject: [PATCH 02/20] feat: optimize runtime when build (#5082) * chore: bump version * feat: analyze runtime * feat: support vite alias * fix: vite config for hooks * fix: add hook * feat: optimize mpa build * fix: mpa generator * fix: compatible with disableRuntime * fix: space size * fix: max old space size * chore: changelog and version * chore: version * chore: optimize code * chore: optimize code * fix: optimize code * fix: optimize code --- packages/build-app-helpers/CHANGELOG.md | 5 + packages/build-app-helpers/package.json | 6 +- .../build-app-helpers/src/analyzeRuntime.ts | 198 ++++++++++++++++++ packages/build-app-helpers/src/index.ts | 2 + .../src/tests/analyzeRuntime.test.ts | 125 +++++++++++ .../tests/fixtures/analyzeRuntime/common.ts | 1 + .../tests/fixtures/analyzeRuntime/index.js | 3 + .../fixtures/analyzeRuntime/page/index.js | 4 + .../fixtures/analyzeRuntime/page/store.js | 2 + .../templates/common/loadStaticModules.ts.ejs | 3 +- .../src/templates/rax/runApp.ts.ejs | 2 +- .../src/templates/react/runApp.ts.ejs | 2 +- packages/build-mpa-config/CHANGELOG.md | 5 + packages/build-mpa-config/package.json | 6 +- .../src/generate/BaseGenerator.ts | 53 +++-- .../src/generate/RaxGenerator.ts | 10 +- .../src/generate/ReactGenerator.ts | 12 +- .../build-mpa-config/src/generate/index.ts | 15 +- packages/build-mpa-config/src/index.ts | 32 ++- .../src/template/rax/index.tsx.ejs | 2 +- .../src/template/react/index.tsx.ejs | 2 +- packages/build-mpa-config/src/types.ts | 5 + packages/icejs/package.json | 4 +- packages/plugin-mpa/CHANGELOG.md | 4 + packages/plugin-mpa/package.json | 6 +- packages/plugin-mpa/src/index.ts | 51 ++++- packages/plugin-react-app/CHANGELOG.md | 5 + packages/plugin-react-app/package.json | 8 +- packages/plugin-react-app/src/config.js | 7 +- packages/plugin-react-app/src/setBuild.js | 19 +- .../vite-plugin-component-style/CHANGELOG.md | 4 + .../vite-plugin-component-style/package.json | 4 +- packages/vite-service/CHANGELOG.md | 4 + packages/vite-service/package.json | 4 +- packages/vite-service/src/build.ts | 9 +- packages/vite-service/src/start.ts | 4 +- yarn.lock | 7 +- 37 files changed, 562 insertions(+), 73 deletions(-) create mode 100644 packages/build-app-helpers/src/analyzeRuntime.ts create mode 100644 packages/build-app-helpers/src/tests/analyzeRuntime.test.ts create mode 100644 packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/common.ts create mode 100644 packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/index.js create mode 100644 packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/index.js create mode 100644 packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/store.js diff --git a/packages/build-app-helpers/CHANGELOG.md b/packages/build-app-helpers/CHANGELOG.md index abdb2a9d81..e2a1c538c6 100644 --- a/packages/build-app-helpers/CHANGELOG.md +++ b/packages/build-app-helpers/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.5.0 + +- [chore] bump version of `es-module-lexer` +- [feat] support scan source code of import statement + ## 2.4.2 - [fix] import redirection with alias diff --git a/packages/build-app-helpers/package.json b/packages/build-app-helpers/package.json index ed893b3d03..0e44c3875f 100644 --- a/packages/build-app-helpers/package.json +++ b/packages/build-app-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@builder/app-helpers", - "version": "2.4.2", + "version": "2.5.0", "description": "build helpers for app framework", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -26,6 +26,8 @@ "@babel/traverse": "^7.12.12", "fs-extra": "^8.1.0", "lodash": "^4.17.20", - "es-module-lexer": "^0.7.1" + "esbuild": "^0.13.12", + "es-module-lexer": "^0.9.0", + "fast-glob": "^3.2.7" } } \ No newline at end of file diff --git a/packages/build-app-helpers/src/analyzeRuntime.ts b/packages/build-app-helpers/src/analyzeRuntime.ts new file mode 100644 index 0000000000..d2762872cd --- /dev/null +++ b/packages/build-app-helpers/src/analyzeRuntime.ts @@ -0,0 +1,198 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as glob from 'fast-glob'; +import { init, parse } from 'es-module-lexer'; +import { transform } from 'esbuild'; +import type { Loader } from 'esbuild'; + +type CheckMap = Record; +type WebpackAlias = Record; +type ViteAlias = {find: string | RegExp, replacement: string}[]; +interface Options { + rootDir: string; + parallel?: number; + analyzeRelativeImport?: boolean; + mode?: 'webpack' | 'vite'; + // webpack mode + alias?: WebpackAlias | ViteAlias; + customRuntimeRules?: Record +} + +const defaultRuntimeRules = { + 'build-plugin-ice-request': ['request', 'useRequest'], + 'build-plugin-ice-auth': ['useAuth', 'withAuth'], +}; + +export async function globSourceFiles(sourceDir: string): Promise { + return await glob('**/*.{js,ts,jsx,tsx}', { + cwd: sourceDir, + ignore: [ + '**/node_modules/**', + 'src/apis/**', + '**/__tests__/**', + ], + absolute: true, + }); +} + +function addLastSlash(filePath: string) { + return filePath.endsWith('/') ? filePath : `${filePath}/`; +} + +function getWebpackAliasedPath(filePath: string, alias: WebpackAlias): string { + let aliasedPath = filePath; + // eslint-disable-next-line no-restricted-syntax + for (const aliasKey of Object.keys(alias || {})) { + const isStrict = aliasKey.endsWith('$'); + const strictKey = isStrict ? aliasKey.slice(0, -1) : aliasKey; + const aliasValue = alias[aliasKey]; + + if (aliasValue.match(/.(j|t)s(x)?$/)) { + if (aliasedPath === strictKey) { + aliasedPath = aliasValue; + break; + } + } else { + // case: { xyz: '/some/dir' }, { xyz$: '/some/dir' } + // import xyz from 'xyz'; // resolved as '/some/dir' + if (aliasedPath === strictKey) { + aliasedPath = aliasValue; + break; + } else if (isStrict) { + // case: { xyz$: '/some/dir' } + // import xyz from 'xyz/file.js'; // resolved as /abc/node_modules/xyz/file.js + // eslint-disable-next-line no-continue + continue; + } + // case: { xyz: '/some/dir' } + // import xyz from 'xyz/file.js'; // resolved as /some/dir/file.js + const slashedKey = addLastSlash(strictKey); + if (aliasedPath.startsWith(slashedKey)) { + aliasedPath = aliasedPath.replace(new RegExp(`^${slashedKey}`), addLastSlash(aliasValue)); + break; + } + } + } + return aliasedPath; +} + +function getViteAliasedPath(filePath: string, alias: ViteAlias): string { + // apply aliases + let aliasedPath = filePath; + // eslint-disable-next-line no-restricted-syntax + for (const { find, replacement } of (alias || [])) { + const matches = + typeof find === 'string' ? aliasedPath.startsWith(find) : find.test(aliasedPath); + if (matches) { + aliasedPath = aliasedPath.replace(find, replacement); + break; + } + } + return aliasedPath; +} + +export function getImportPath(importSpecifier: string, importer: string, options: Pick) { + const { alias, rootDir, mode } = options; + let aliasedPath = mode === 'webpack' + ? getWebpackAliasedPath(importSpecifier, alias as WebpackAlias) + : getViteAliasedPath(importSpecifier, alias as ViteAlias); + if (!path.isAbsolute(aliasedPath)) { + try { + // 检测是否可以在 node_modules 下找到依赖,如果可以直接使用该依赖 + aliasedPath = require.resolve(aliasedPath, { paths: [rootDir]}); + } catch (e) { + // ignore errors + aliasedPath = path.resolve(path.dirname(importer), aliasedPath); + } + } + // filter path with node_modules + if (aliasedPath.includes('node_modules')) { + return ''; + } else if (!path.extname(aliasedPath)) { + // get index file of + const basename = path.basename(aliasedPath); + const patterns = [`${basename}.{js,ts,jsx,tsx}`, `${basename}/index.{js,ts,jsx,tsx}`]; + + return glob.sync(patterns, { + cwd: path.dirname(aliasedPath), + absolute: true, + })[0]; + } + return aliasedPath; +} + +export default async function analyzeRuntime(files: string[], options: Options): Promise { + const { analyzeRelativeImport, rootDir, alias, mode = 'webpack', parallel, customRuntimeRules = {} } = options; + const parallelNum = parallel ?? 10; + const sourceFiles = [...files]; + const checkMap: CheckMap = {}; + const runtimeRules = { ...defaultRuntimeRules, ...customRuntimeRules }; + // init check map + const checkPlugins = Object.keys(runtimeRules); + checkPlugins.forEach((pluginName) => { + checkMap[pluginName] = false; + }); + + async function analyzeFile(filePath: string) { + let source = fs.readFileSync(filePath, 'utf-8'); + const lang = path.extname(filePath).slice(1); + let loader: Loader; + if (lang === 'ts' || lang === 'tsx') { + loader = lang; + } + try { + if (loader) { + // transform content first since es-module-lexer can't handle ts file + source = (await transform(source, { loader })).code; + } + await init; + const imports = parse(source)[0]; + await Promise.all(imports.map((importSpecifier) => { + return (async () => { + const importName = importSpecifier.n; + // filter source code + if (importName === 'ice') { + const importStr = source.substring(importSpecifier.ss, importSpecifier.se); + checkPlugins.forEach((pluginName) => { + const apiList: string[] = runtimeRules[pluginName]; + if (apiList.some((apiName) => importStr.includes(apiName))) { + checkMap[pluginName] = true; + } + }); + } else if (analyzeRelativeImport) { + let importPath = importName; + if (!path.isAbsolute(importPath)) { + importPath = getImportPath(importPath, filePath, { rootDir, alias, mode }); + } + if (importPath && !sourceFiles.includes(importPath) && fs.existsSync(importPath)) { + await analyzeFile(importPath); + } + } + })(); + })); + } catch (err) { + console.log('[ERROR]', `optimize runtime failed when analyze ${filePath}`); + throw err; + } + } + + try { + for ( + let i = 0; + i * parallelNum < sourceFiles.length && checkPlugins.some((pluginName) => checkMap[pluginName] === false); + i++) { + // eslint-disable-next-line no-await-in-loop + await Promise.all(sourceFiles.slice(i * parallelNum, parallelNum).map((filePath) => { + return analyzeFile(filePath); + })); + } + } catch(err) { + // 如果发生错误,兜底启用所有自动检测的运行时插件,防止错误地移除 + checkPlugins.forEach((pluginName) => { + checkMap[pluginName] = true; + }); + return checkMap; + } + + return checkMap; +} \ No newline at end of file diff --git a/packages/build-app-helpers/src/index.ts b/packages/build-app-helpers/src/index.ts index 82d88790eb..3603530385 100644 --- a/packages/build-app-helpers/src/index.ts +++ b/packages/build-app-helpers/src/index.ts @@ -9,3 +9,5 @@ export { default as checkExportDefaultDeclarationExists } from './checkExportDef export { default as getSassImplementation } from './getSassImplementation'; export { default as getLessImplementation } from './getLessImplementation'; export { default as redirectImport } from './redirectImport'; +export { default as analyzeRuntime } from './analyzeRuntime'; +export { globSourceFiles } from './analyzeRuntime'; diff --git a/packages/build-app-helpers/src/tests/analyzeRuntime.test.ts b/packages/build-app-helpers/src/tests/analyzeRuntime.test.ts new file mode 100644 index 0000000000..1abe80d458 --- /dev/null +++ b/packages/build-app-helpers/src/tests/analyzeRuntime.test.ts @@ -0,0 +1,125 @@ +import path = require('path'); +import analyzeRuntime, { globSourceFiles, getImportPath } from '../analyzeRuntime'; + +describe('get source file', () => { + const rootDir = path.join(__dirname, './fixtures/analyzeRuntime/'); + it('glob js/ts files', async () => { + const files = await globSourceFiles(rootDir); + expect(files.length).toBe(4); + }); +}); + +describe('get aliased path', () => { + const rootDir = path.join(__dirname, './fixtures/analyzeRuntime/'); + it('webpack alias: { ice: \'./runApp\' }', () => { + const aliasedPath = getImportPath('ice', path.join(rootDir, 'index.js'), { + rootDir, + alias: { ice: './runApp' }, + mode: 'webpack', + }); + // file not exists throw error + expect(aliasedPath).toBe(undefined); + }); + + it('get relative path with mode webpack', () => { + const aliasedPath = getImportPath('./store', path.join(rootDir, 'page/index.js'), { + rootDir, + alias: {}, + mode: 'webpack', + }); + // file not exists throw error + expect(aliasedPath).toBe(path.join(rootDir, 'page/store.js')); + }); + + it('get relative path with mode vite', () => { + const aliasedPath = getImportPath('./store', path.join(rootDir, 'page/index.js'), { + rootDir, + alias: {}, + mode: 'vite', + }); + // file not exists throw error + expect(aliasedPath).toBe(path.join(rootDir, 'page/store.js')); + }); + + it('webpack alias: { ice: \'react\' }', () => { + const aliasedPath = getImportPath('ice', path.join(rootDir, 'index.js'), { + rootDir, + alias: { ice: 'react' }, + mode: 'webpack', + }); + expect(aliasedPath).toBe(''); + }); + + it('webpack alias: { @: \'rootDir\' }', () => { + const aliasedPath = getImportPath('@/page', path.join(rootDir, 'index.js'), { + rootDir, + alias: { '@': rootDir }, + mode: 'webpack', + }); + expect(aliasedPath).toBe(path.join(rootDir, 'page/index.js')); + }); + + it('webpack alias: { @$: \'rootDir\' }', () => { + const aliasedPath = getImportPath('@/page', path.join(rootDir, 'index.js'), { + rootDir, + alias: { '@$': rootDir }, + mode: 'webpack', + }); + // without match any alias rule, throw error + expect(aliasedPath).toBe(undefined); + }); + + it('vite alias: [{find: \'ice\', replacement: \'react\'}]', () => { + const aliasedPath = getImportPath('ice', path.join(rootDir, 'index.js'), { + rootDir, + alias: [{ find: 'ice', replacement: 'react' }], + mode: 'vite', + }); + // filter node_modules dependencies + expect(aliasedPath).toBe(''); + }); + + it('vite alias: [{find: \'@\', replacement: \'rootDir\'}]', () => { + const aliasedPath = getImportPath('@/page', path.join(rootDir, 'index.js'), { + rootDir, + alias: [{ find: '@', replacement: rootDir }], + mode: 'vite', + }); + expect(aliasedPath).toBe(path.join(rootDir, 'page/index.js')); + }); + + it('vite alias: [{find: \/@$\/, replacement: \'rootDir\'}]', () => { + const aliasedPath = getImportPath('@/page', path.join(rootDir, 'index.js'), { + rootDir, + alias: [{ find: /@$/, replacement: rootDir }], + mode: 'vite', + }); + // without match any alias rule, throw error + expect(aliasedPath).toBe(undefined); + }); +}); + +describe('Analyze Runtime', () => { + const rootDir = path.join(__dirname, './fixtures/analyzeRuntime/'); + const entryFile = path.join(rootDir, 'index.js'); + + it('analyze entry file', async () => { + const checkMap = await analyzeRuntime([entryFile], { rootDir }); + expect(checkMap).toStrictEqual({ + 'build-plugin-ice-request': true, + 'build-plugin-ice-auth': false, + }); + }); + + it('analyze relative import', async () => { + const checkMap = await analyzeRuntime( + [entryFile], + { rootDir, analyzeRelativeImport: true, alias: { '@': path.join(rootDir)}, customRuntimeRules: { 'build-plugin-ice-store': ['createStore']} } + ); + expect(checkMap).toStrictEqual({ + 'build-plugin-ice-request': true, + 'build-plugin-ice-auth': true, + 'build-plugin-ice-store': true, + }); + }); +}); \ No newline at end of file diff --git a/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/common.ts b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/common.ts new file mode 100644 index 0000000000..e1d7bcf332 --- /dev/null +++ b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/common.ts @@ -0,0 +1 @@ +export default function common() {}; \ No newline at end of file diff --git a/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/index.js b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/index.js new file mode 100644 index 0000000000..d86cab0823 --- /dev/null +++ b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/index.js @@ -0,0 +1,3 @@ +import { useRequest } from 'ice'; +import { page } from '@/page'; +import common from '@/common'; \ No newline at end of file diff --git a/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/index.js b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/index.js new file mode 100644 index 0000000000..41b30175bc --- /dev/null +++ b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/index.js @@ -0,0 +1,4 @@ +import { useAuth } from 'ice'; +import store from './store'; + +export const page = () => {}; \ No newline at end of file diff --git a/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/store.js b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/store.js new file mode 100644 index 0000000000..25011710b9 --- /dev/null +++ b/packages/build-app-helpers/src/tests/fixtures/analyzeRuntime/page/store.js @@ -0,0 +1,2 @@ +import { createStore } from 'ice'; +export default createStore; \ No newline at end of file diff --git a/packages/build-app-templates/src/templates/common/loadStaticModules.ts.ejs b/packages/build-app-templates/src/templates/common/loadStaticModules.ts.ejs index 43fa0170b5..11f0972f10 100644 --- a/packages/build-app-templates/src/templates/common/loadStaticModules.ts.ejs +++ b/packages/build-app-templates/src/templates/common/loadStaticModules.ts.ejs @@ -1,10 +1,9 @@ -import { IAppConfig } from '../types'; - <% const moduleNames = []; %> <% if (runtimeModules.length) {%> <% runtimeModules.filter((moduleInfo) => moduleInfo.staticModule).forEach((runtimeModule, index) => { %> <% moduleNames.push('module' + index) %>import module<%= index %> from '<%= runtimeModule.path %>';<% }) %> <% } %> +import type { IAppConfig } from '<%- typesPath %>'; function loadStaticModules(appConfig: IAppConfig) { <% if (moduleNames.length) {%> diff --git a/packages/build-app-templates/src/templates/rax/runApp.ts.ejs b/packages/build-app-templates/src/templates/rax/runApp.ts.ejs index 15777189a2..1f8dea6cfc 100644 --- a/packages/build-app-templates/src/templates/rax/runApp.ts.ejs +++ b/packages/build-app-templates/src/templates/rax/runApp.ts.ejs @@ -19,7 +19,7 @@ import raxAppRenderer, { RenderAppConfig } from 'rax-app-renderer'; // eslint-disable-next-line import '<%= globalStyle %>'; <% } %> -import loadStaticModules from '<%- relativeCorePath %>/loadStaticModules'; +import loadStaticModules from './loadStaticModules'; import loadRuntimeModules from './loadRuntimeModules'; import { setAppConfig } from '<%- relativeCorePath %>/appConfig'; diff --git a/packages/build-app-templates/src/templates/react/runApp.ts.ejs b/packages/build-app-templates/src/templates/react/runApp.ts.ejs index 0f86d1e118..7a564451e7 100644 --- a/packages/build-app-templates/src/templates/react/runApp.ts.ejs +++ b/packages/build-app-templates/src/templates/react/runApp.ts.ejs @@ -15,7 +15,7 @@ import reactAppRenderer, { RenderAppConfig } from 'react-app-renderer'; // eslint-disable-next-line import '<%= globalStyle %>' <% } %> -import loadStaticModules from '<%- relativeCorePath %>/loadStaticModules'; +import loadStaticModules from './loadStaticModules'; import loadRuntimeModules from './loadRuntimeModules'; import { setAppConfig } from '<%- relativeCorePath %>/appConfig'; diff --git a/packages/build-mpa-config/CHANGELOG.md b/packages/build-mpa-config/CHANGELOG.md index 0f76cd7637..e547715f88 100644 --- a/packages/build-mpa-config/CHANGELOG.md +++ b/packages/build-mpa-config/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 4.2.0 + +- [feat] support disable runtime before render template +- [fix] file path render in ejs template + ## 4.1.3 - [fix] fix: format mpa `app.json` path in windows diff --git a/packages/build-mpa-config/package.json b/packages/build-mpa-config/package.json index c9a49490e5..e01faba26c 100644 --- a/packages/build-mpa-config/package.json +++ b/packages/build-mpa-config/package.json @@ -1,6 +1,6 @@ { "name": "@builder/mpa-config", - "version": "4.1.3", + "version": "4.2.0", "description": "enable mpa project for framework", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -21,7 +21,7 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "@builder/app-helpers": "^2.4.2", + "@builder/app-helpers": "^2.5.0", "fs-extra": "^8.1.0", "loader-utils": "^2.0.0", "globby": "^11.0.2", @@ -30,4 +30,4 @@ "devDependencies": { "build-scripts": "^1.1.0" } -} +} \ No newline at end of file diff --git a/packages/build-mpa-config/src/generate/BaseGenerator.ts b/packages/build-mpa-config/src/generate/BaseGenerator.ts index 455b9983c8..cf3ef17212 100644 --- a/packages/build-mpa-config/src/generate/BaseGenerator.ts +++ b/packages/build-mpa-config/src/generate/BaseGenerator.ts @@ -26,7 +26,7 @@ export default class BaseGenerator { public routesFilePath: string; - public disableRuntimeList: string[]; + public disableRuntimeList: string[] = []; public runAppRenderData: IRunAppRenderData = {}; @@ -44,6 +44,9 @@ export default class BaseGenerator { this.entryFolder = path.join(targetDir, 'entries', entryName); this.entryPath = path.join(this.entryFolder, 'index.tsx'); this.runAppPath = path.join(this.entryFolder, 'runApp'); + this.runAppRenderData = { + typesPath: relative(this.entryFolder, path.join(this.targetDir, 'types')), + }; } /** @@ -59,7 +62,6 @@ export default class BaseGenerator { ...this.runAppRenderData, globalStyle: globalStyles.length && relative(this.entryFolder, path.join(this.rootDir, globalStyles[0])), relativeCorePath: relative(this.entryFolder, path.join(this.targetDir, 'core')), - typesPath: relative(this.entryFolder, path.join(this.targetDir, 'types')), buildConfig: { ...applyMethod('getBuildConfig', userConfig), }, @@ -69,7 +71,7 @@ export default class BaseGenerator { }; applyMethod('addRenderFile', getTemplate('runApp.ts', framework), `${this.runAppPath}.ts`, renderData); - this.generateLoadRuntimeModules(routesFilePath); + this.generateLoadRuntimeModules(); } public generateEntryFile() { @@ -77,41 +79,46 @@ export default class BaseGenerator { const { applyMethod } = this.builtInMethods; const routesFilePath = this.getRoutesFilePath(); const renderData = { + ...this.runAppRenderData, runAppPath: './runApp', - typesPath: relative(this.entryFolder, path.join(this.targetDir, 'types')), routesFilePath: routesFilePath && relative(this.entryFolder, routesFilePath), resourcePath: relative(this.entryFolder, path.extname(pageEntry) ? pageEntry.split('.').slice(0, -1).join('.') : pageEntry), }; applyMethod('addRenderFile', path.join(__dirname, `../template/${framework}/index.tsx.ejs`), this.entryPath, renderData); } - public generateLoadRuntimeModules(routesFilePath: string) { + public generateLoadRuntimeModules() { const { applyMethod } = this.builtInMethods; - applyMethod('addRenderFile', getTemplate('loadRuntimeModules.ts'), path.join(this.entryFolder, 'loadRuntimeModules.ts') - , (renderData) => { - let { runtimeModules } = renderData; - if (!routesFilePath) { - // MPA 某个 page 下面没有 routes.[j|t]s 文件,禁用 plugin-router 运行时能力 + ['loadRuntimeModules.ts', 'loadStaticModules.ts'].forEach((templateName) => { + applyMethod('addRenderFile', getTemplate(templateName), path.join(this.entryFolder, templateName) + , (renderData) => { + let { runtimeModules } = renderData; runtimeModules = runtimeModules.filter(({ name }) => { return !this.disableRuntimeList.includes(name); }); - } - return { - ...renderData, - runtimeModules: runtimeModules.map(({ path: pluginPath, staticModule, absoluteModulePath }) => { - if (!staticModule) { + return { + ...renderData, + ...this.runAppRenderData, + relativeTypePath: relative(this.entryFolder, path.join(this.targetDir, 'type')), + runtimeModules: runtimeModules.map(({ path: pluginPath, staticModule, absoluteModulePath }) => { pluginPath = relative(this.entryFolder, absoluteModulePath); - } - return { - path: pluginPath, - staticModule, - }; - }), - }; - }); + return { + path: pluginPath, + staticModule, + }; + }), + }; + }); + }); } public getRoutesFilePath() :string { throw new Error('Method not implemented.'); } + + public addDisableRuntime = (pluginName: string) => { + if (!this.disableRuntimeList.includes(pluginName)) { + this.disableRuntimeList.push(pluginName); + } + } } diff --git a/packages/build-mpa-config/src/generate/RaxGenerator.ts b/packages/build-mpa-config/src/generate/RaxGenerator.ts index a70803055d..f9303048a8 100644 --- a/packages/build-mpa-config/src/generate/RaxGenerator.ts +++ b/packages/build-mpa-config/src/generate/RaxGenerator.ts @@ -4,14 +4,18 @@ import Base from './BaseGenerator'; import relative from '../relative'; export default class RaxGenerator extends Base { - public disableRuntimeList = ['build-plugin-rax-router'] - constructor(api, options) { super(api, options); this.addRunAppRenderData(); this.injectTabBar(); + this.routesFilePath = this.getRoutesFilePath(); + if (!this.routesFilePath) { + this.addDisableRuntime(this.routerPluginName); + } } + public routerPluginName = 'build-plugin-rax-router'; + private injectTabBar() { const { getValue } = this.builtInMethods; const { isAppEntry } = this.options; @@ -63,6 +67,6 @@ export default class RaxGenerator extends Base { const { pageEntry } = this.options; const originalEntryFolder = path.dirname(pageEntry); const appJSONPath = path.join(originalEntryFolder, 'app.json'); - return this.routesFilePath = fs.existsSync(appJSONPath) ? appJSONPath : ''; + return fs.existsSync(appJSONPath) ? appJSONPath : ''; } } diff --git a/packages/build-mpa-config/src/generate/ReactGenerator.ts b/packages/build-mpa-config/src/generate/ReactGenerator.ts index 683d7299ba..35d3c8aab7 100644 --- a/packages/build-mpa-config/src/generate/ReactGenerator.ts +++ b/packages/build-mpa-config/src/generate/ReactGenerator.ts @@ -5,13 +5,21 @@ import Base from './BaseGenerator'; import relative from '../relative'; export default class ReactGenerator extends Base { - public disableRuntimeList = ['build-plugin-ice-router'] + constructor(api, options) { + super(api, options); + this.routesFilePath = this.getRoutesFilePath(); + if (!this.routesFilePath) { + this.addDisableRuntime(this.routerPluginName); + } + } + + public routerPluginName = 'build-plugin-ice-router'; public getRoutesFilePath(): string { if (this.routesFilePath !== undefined) return this.routesFilePath; const { pageEntry } = this.options; const originalEntryFolder = path.dirname(pageEntry); const targetExt = ['ts', 'tsx', 'js', 'jsx'].find((ext) => fs.existsSync(path.join(originalEntryFolder, `routes.${ext}`))); - return this.routesFilePath = targetExt ? relative(this.entryFolder, path.join(originalEntryFolder, 'routes')) : ''; + return targetExt ? relative(this.entryFolder, path.join(originalEntryFolder, 'routes')) : ''; } } diff --git a/packages/build-mpa-config/src/generate/index.ts b/packages/build-mpa-config/src/generate/index.ts index b1ba9f04a5..0773c2c710 100644 --- a/packages/build-mpa-config/src/generate/index.ts +++ b/packages/build-mpa-config/src/generate/index.ts @@ -13,19 +13,30 @@ function generatePageFiles(api: IPluginAPI, options: IGeneratorOptions): IGenera } const { context: { userConfig } } = api; - generator.generateRunAppFile(userConfig); + // 在分析运行时依赖的场景下,不能直接执行 generator + // 需要在分析完成并执行 disableRuntimePlugins 逻辑后,再执行生成 + const generateTasks = []; + generateTasks.push(() => { + generator.generateRunAppFile(userConfig); + }); // Do not modify the page entry when entryPath ends with app.ts if (isAppEntry) { return { + generator, + generateTasks, entryPath: pageEntry, runAppPath: generator.runAppPath, routesFilePath: generator.routesFilePath, }; } + generateTasks.push(() => { + generator.generateEntryFile(); + }); - generator.generateEntryFile(); return { + generator, + generateTasks, entryPath: generator.entryPath, runAppPath: generator.runAppPath, routesFilePath: generator.routesFilePath, diff --git a/packages/build-mpa-config/src/index.ts b/packages/build-mpa-config/src/index.ts index 557a46168f..7a5298fa26 100644 --- a/packages/build-mpa-config/src/index.ts +++ b/packages/build-mpa-config/src/index.ts @@ -1,6 +1,8 @@ import * as path from 'path'; import { formatPath, checkExportDefaultDeclarationExists } from '@builder/app-helpers'; import { IPluginAPI } from 'build-scripts'; +import type ReactGenerator from './generate/ReactGenerator'; +import type RaxGenerator from './generate/RaxGenerator'; import generateEntry from './generate'; import { FrameworkType, IGenerateResult } from './types'; @@ -16,11 +18,19 @@ interface IConfigOptions { framework?: FrameworkType; entries?: IEntries[]; targetDir?: string; + executeGenerateTasks?: boolean; } -export const generateMPAEntries = (api: IPluginAPI, options: IConfigOptions) => { +interface MPAEntries extends IEntries { + generator?: RaxGenerator | ReactGenerator; + finalEntry: string; + runAppPath: string | null; + routesFilePath: string | null; +} + +export const generateMPAEntries = (api: IPluginAPI, options: IConfigOptions): Record => { const { context } = api; - const { framework = 'rax', targetDir = '' } = options; + const { framework = 'rax', targetDir = '', executeGenerateTasks = true } = options; let { entries } = options; const { rootDir, commandArgs } = context; if (commandArgs.mpaEntry) { @@ -39,15 +49,27 @@ export const generateMPAEntries = (api: IPluginAPI, options: IConfigOptions) => let finalEntry = entryPath; let runAppPath = null; let routesFilePath; + let generator = null; + let generateTasks = []; if (isAppEntry || checkExportDefaultDeclarationExists(path.join(rootDir, 'src', source))) { const result = generateEntry(api, { framework, targetDir, pageEntry: entryPath, entryName, pageConfig, isAppEntry }); + if (executeGenerateTasks) { + result.generateTasks.forEach((generateTask) => { + generateTask(); + }); + } else { + generateTasks = result.generateTasks; + } finalEntry = result.entryPath; runAppPath = result.runAppPath; routesFilePath = result.routesFilePath; + generator = result.generator; } parsedEntries[entryName] = { ...entry, + generator, + generateTasks, finalEntry, runAppPath, routesFilePath, @@ -93,10 +115,14 @@ const setMPAConfig = (api, config, options: IConfigOptions) => { }); if (config.plugins.has('document')) { + const filteredEntries = { ...parsedEntries }; + // remove property which is unnecessary for plugin document + delete filteredEntries.generator; + delete filteredEntries.generateTasks; config.plugin('document').tap(args => { return [{ ...args[0], - pages: Object.values(parsedEntries), + pages: Object.values(filteredEntries), }]; }); } diff --git a/packages/build-mpa-config/src/template/rax/index.tsx.ejs b/packages/build-mpa-config/src/template/rax/index.tsx.ejs index 8cccb5a022..02641f083f 100644 --- a/packages/build-mpa-config/src/template/rax/index.tsx.ejs +++ b/packages/build-mpa-config/src/template/rax/index.tsx.ejs @@ -1,5 +1,5 @@ import { runApp } from '<%- runAppPath %>'; -import { IAppConfig } from '<%- typesPath %>'; +import type { IAppConfig } from '<%- typesPath %>'; <% if (routesFilePath) {%> import staticConfig from '<%- routesFilePath %>'; diff --git a/packages/build-mpa-config/src/template/react/index.tsx.ejs b/packages/build-mpa-config/src/template/react/index.tsx.ejs index 672abba863..0e01296358 100644 --- a/packages/build-mpa-config/src/template/react/index.tsx.ejs +++ b/packages/build-mpa-config/src/template/react/index.tsx.ejs @@ -1,5 +1,5 @@ import { runApp } from '<%- runAppPath %>'; -import { IAppConfig } from '<%- typesPath %>'; +import type { IAppConfig } from '<%- typesPath %>'; <% if (routesFilePath) {%> import routes from '<%- routesFilePath %>'; diff --git a/packages/build-mpa-config/src/types.ts b/packages/build-mpa-config/src/types.ts index de7a8f3d08..d4c42e5893 100644 --- a/packages/build-mpa-config/src/types.ts +++ b/packages/build-mpa-config/src/types.ts @@ -1,3 +1,6 @@ +import type RaxGenerator from './generate/RaxGenerator'; +import type ReactGenerator from './generate/ReactGenerator'; + export interface IGeneratorOptions { targetDir: string; entryName: string; @@ -11,6 +14,8 @@ export interface IGenerateResult { entryPath: string; runAppPath: string; routesFilePath: string | undefined; + generator?: RaxGenerator | ReactGenerator; + generateTasks?: (() => void)[]; } export type FrameworkType = 'rax' | 'react'; diff --git a/packages/icejs/package.json b/packages/icejs/package.json index b1bc73140d..38f7969f89 100644 --- a/packages/icejs/package.json +++ b/packages/icejs/package.json @@ -29,12 +29,12 @@ "build-plugin-ice-auth": "2.0.1", "build-plugin-ice-config": "2.0.2", "build-plugin-ice-logger": "2.0.0", - "build-plugin-ice-mpa": "2.0.4", + "build-plugin-ice-mpa": "2.1.0", "build-plugin-ice-request": "2.0.0", "build-plugin-ice-router": "2.1.1", "build-plugin-ice-ssr": "3.0.6", "build-plugin-ice-store": "2.0.6", - "build-plugin-react-app": "2.1.4", + "build-plugin-react-app": "2.2.0", "build-plugin-pwa": "1.1.0", "build-plugin-speed": "1.0.0", "chalk": "^4.1.0", diff --git a/packages/plugin-mpa/CHANGELOG.md b/packages/plugin-mpa/CHANGELOG.md index f1919d8d94..2f8920a1af 100644 --- a/packages/plugin-mpa/CHANGELOG.md +++ b/packages/plugin-mpa/CHANGELOG.md @@ -1,5 +1,9 @@ # changelog +## 2.1.0 + +- [feat] optimize runtime when build + ## 2.0.4 - [fix] format path in case of win32 system diff --git a/packages/plugin-mpa/package.json b/packages/plugin-mpa/package.json index d482b0d0df..86941ba1ac 100644 --- a/packages/plugin-mpa/package.json +++ b/packages/plugin-mpa/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-ice-mpa", - "version": "2.0.4", + "version": "2.1.0", "description": "enable mpa project for icejs framework", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -24,8 +24,8 @@ "build-scripts": "^1.1.0" }, "dependencies": { - "@builder/app-helpers": "^2.4.1", - "@builder/mpa-config": "^4.1.2", + "@builder/app-helpers": "^2.5.0", + "@builder/mpa-config": "^4.2.0", "fs-extra": "^8.1.0" } } \ No newline at end of file diff --git a/packages/plugin-mpa/src/index.ts b/packages/plugin-mpa/src/index.ts index 8a99529964..410458b4ad 100644 --- a/packages/plugin-mpa/src/index.ts +++ b/packages/plugin-mpa/src/index.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as fs from 'fs-extra'; -import { getMpaEntries, formatPath } from '@builder/app-helpers'; +import { getMpaEntries, formatPath, analyzeRuntime } from '@builder/app-helpers'; import { generateMPAEntries } from '@builder/mpa-config'; import { IPlugin } from 'build-scripts'; @@ -13,9 +13,11 @@ interface IMpaConfig { rewrites?: {[key: string]: string} | boolean; } +type Mode = 'webpack' | 'vite'; + const plugin: IPlugin = (api) => { - const { context, registerUserConfig, registerCliOption, modifyUserConfig, onGetWebpackConfig, log, setValue, getValue, applyMethod } = api; - const { rootDir, userConfig, commandArgs } = context; + const { context, registerUserConfig, registerCliOption, modifyUserConfig, onGetWebpackConfig, log, setValue, getValue, applyMethod, onHook } = api; + const { rootDir, userConfig, commandArgs, command } = context; const { mpa } = userConfig; // register mpa in build.json @@ -81,7 +83,12 @@ const plugin: IPlugin = (api) => { // compatible with undefined TEMP_PATH // if disableRuntime is true, do not generate mpa entries if (getValue('TEMP_PATH')) { - parsedEntries = generateMPAEntries(api, { entries: mpaEntries, framework: 'react', targetDir: getValue('TEMP_PATH') }); + parsedEntries = generateMPAEntries(api, { + executeGenerateTasks: command !== 'build' || !userConfig.optimizeRuntime, + entries: mpaEntries, + framework: 'react', + targetDir: getValue('TEMP_PATH'), + }); } let finalMPAEntries = {}; if (parsedEntries) { @@ -95,6 +102,42 @@ const plugin: IPlugin = (api) => { }); } }); + if (userConfig.optimizeRuntime) { + onHook('before.build.load', async (options) => { + await Promise.all(Object.keys(parsedEntries).map(async (entryKey) => { + const { generator, generateTasks, entryPath } = parsedEntries[entryKey]; + // 仅针对使用了运行时能力的入口进行分析 + if (generator) { + const { viteConfig, webpackConfig } = options as any; + let alias; + let mode: Mode = 'webpack'; + if (viteConfig) { + alias = viteConfig?.resolve?.alias; + mode = 'vite'; + } else if (webpackConfig) { + alias = webpackConfig?.[0].chainConfig?.toConfig?.()?.resolve?.alias; + } + const runtimeUsedMap = await analyzeRuntime([entryPath], { + // SPA 下有目录规范上更加精准的判断, MPA 通过 createStore 引入进行判断 + customRuntimeRules: { 'build-plugin-ice-store': ['createStore'] }, + rootDir, + mode, + alias, + analyzeRelativeImport: true, + }); + Object.keys(runtimeUsedMap).forEach((pluginName) => { + const isUsed = runtimeUsedMap[pluginName]; + if (!isUsed) { + generator.addDisableRuntime(pluginName); + } + }); + (generateTasks || []).forEach((generateTask) => { + generateTask(); + }); + } + })); + }); + } } else { finalMPAEntries = entries; } diff --git a/packages/plugin-react-app/CHANGELOG.md b/packages/plugin-react-app/CHANGELOG.md index e13542de8d..15737e081d 100644 --- a/packages/plugin-react-app/CHANGELOG.md +++ b/packages/plugin-react-app/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.2.0 + +- [feat] optimize runtime when build +- [chore] bump version of `es-module-lexer` + ## 2.1.4 - [fix] lazy import is invalid with swc(`@builder/user-config^2.0.3`) diff --git a/packages/plugin-react-app/package.json b/packages/plugin-react-app/package.json index 67fadb0aab..c753f7006b 100644 --- a/packages/plugin-react-app/package.json +++ b/packages/plugin-react-app/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-react-app", - "version": "2.1.4", + "version": "2.2.0", "description": "The basic webpack configuration for ice project", "author": "ice-admin@alibaba-inc.com", "main": "lib/index.js", @@ -13,7 +13,7 @@ ], "license": "MIT", "dependencies": { - "@builder/app-helpers": "^2.4.2", + "@builder/app-helpers": "^2.5.0", "@builder/jest-config": "^2.0.0", "@builder/pack": "^0.5.0", "@builder/user-config": "^2.0.3", @@ -21,7 +21,7 @@ "chalk": "^4.0.0", "debug": "^4.1.1", "deepmerge": "^4.2.2", - "es-module-lexer": "^0.4.1", + "es-module-lexer": "^0.9.0", "esbuild": "^0.13.12", "events": "^3.3.0", "fast-glob": "^3.2.5", @@ -38,7 +38,7 @@ "react-refresh": "^0.10.0", "react-router": "^5.2.1", "react-router-dom": "^5.1.2", - "vite-plugin-component-style": "^1.0.3", + "vite-plugin-component-style": "^1.0.4", "webpack-plugin-import": "^0.3.0" }, "devDependencies": { diff --git a/packages/plugin-react-app/src/config.js b/packages/plugin-react-app/src/config.js index e781ab8bfa..9657c6cd2b 100644 --- a/packages/plugin-react-app/src/config.js +++ b/packages/plugin-react-app/src/config.js @@ -54,6 +54,11 @@ module.exports = function() { defaultValue: true, validation: 'boolean', configWebpack: require('./userConfig/fastRefresh'), - } + }, + { + name: 'optimizeRuntime', + defaultValue: true, + validation: 'boolean', + }, ]; }; diff --git a/packages/plugin-react-app/src/setBuild.js b/packages/plugin-react-app/src/setBuild.js index eab3d87dfe..e9aa0f3db0 100644 --- a/packages/plugin-react-app/src/setBuild.js +++ b/packages/plugin-react-app/src/setBuild.js @@ -1,8 +1,25 @@ +const path = require('path'); const HtmlWebpackPlugin = require('@builder/pack/deps/html-webpack-plugin'); +const { analyzeRuntime, globSourceFiles } = require('@builder/app-helpers'); const { getWebOutputPath, logWebpackConfig } = require('./utils'); module.exports = (api) => { - const { context, onHook, onGetWebpackConfig } = api; + const { context, onHook, onGetWebpackConfig, applyMethod } = api; + const { userConfig, rootDir } = context; + + if (userConfig.optimizeRuntime && !userConfig.mpa && !userConfig.disableRuntime) { + // analyze source folder when SPA + onHook('before.build.load', async () => { + const sourceFiles = await globSourceFiles(path.join(rootDir, 'src')); + const runtimeUsedMap = await analyzeRuntime(sourceFiles, { rootDir }); + Object.keys(runtimeUsedMap).forEach((pluginName) => { + const isUsed = runtimeUsedMap[pluginName]; + if (!isUsed) { + applyMethod('addDisableRuntimePlugin', pluginName); + } + }); + }); + } onHook('before.build.run', ({ config }) => { logWebpackConfig(config); diff --git a/packages/vite-plugin-component-style/CHANGELOG.md b/packages/vite-plugin-component-style/CHANGELOG.md index 832fdd0b2a..0649203492 100644 --- a/packages/vite-plugin-component-style/CHANGELOG.md +++ b/packages/vite-plugin-component-style/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.4 + +- [chore] bump version of `es-module-lexer` + ## 1.0.3 - [fix] style import for multiple components with `stylePath` diff --git a/packages/vite-plugin-component-style/package.json b/packages/vite-plugin-component-style/package.json index 0c5767a460..d26fcb85f4 100644 --- a/packages/vite-plugin-component-style/package.json +++ b/packages/vite-plugin-component-style/package.json @@ -1,6 +1,6 @@ { "name": "vite-plugin-component-style", - "version": "1.0.3", + "version": "1.0.4", "description": "", "main": "lib/index.js", "scripts": { @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@rollup/pluginutils": "^4.1.1", - "es-module-lexer": "^0.4.1", + "es-module-lexer": "^0.9.0", "find-up": "^5.0.0", "fs-extra": "^10.0.0", "magic-string": "^0.25.7" diff --git a/packages/vite-service/CHANGELOG.md b/packages/vite-service/CHANGELOG.md index b18eb35d14..badd8a2c38 100644 --- a/packages/vite-service/CHANGELOG.md +++ b/packages/vite-service/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.0.4 + +- [fix] pass vite config for lifecycle hooks + ## 2.0.3 - [fix] open both `about:blank` page and `localhost:3333` page diff --git a/packages/vite-service/package.json b/packages/vite-service/package.json index 4e5320c806..720e3fe5d6 100644 --- a/packages/vite-service/package.json +++ b/packages/vite-service/package.json @@ -1,6 +1,6 @@ { "name": "@builder/vite-service", - "version": "2.0.3", + "version": "2.0.4", "description": "vite implementation", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -22,7 +22,7 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "@builder/app-helpers": "^2.4.2", + "@builder/app-helpers": "^2.5.0", "@rollup/pluginutils": "^4.1.1", "@vitejs/plugin-legacy": "^1.5.0", "@vitejs/plugin-react": "^1.0.7", diff --git a/packages/vite-service/src/build.ts b/packages/vite-service/src/build.ts index fcc6ab1a79..512397eaaf 100644 --- a/packages/vite-service/src/build.ts +++ b/packages/vite-service/src/build.ts @@ -9,9 +9,14 @@ export async function viteBuild(context: any): Promise { const { applyHook, command, commandArgs } = context; const configArr = context.getWebpackConfig(); - await applyHook(`before.${command}.load`, { args: commandArgs, webpackConfig: configArr }); + const prodConfig = configArr.length > 0 ? wp2vite(context) : {}; + await applyHook(`before.${command}.load`, { args: commandArgs, webpackConfig: configArr, viteConfig: prodConfig }); - const prodConfig = wp2vite(context); + if (!configArr.length) { + const errorMsg = 'No config found.'; + await applyHook('error', { err: new Error(errorMsg) }); + return; + } await applyHook(`before.${command}.run`, { args: commandArgs, diff --git a/packages/vite-service/src/start.ts b/packages/vite-service/src/start.ts index 75cde368b5..71f062fbe0 100644 --- a/packages/vite-service/src/start.ts +++ b/packages/vite-service/src/start.ts @@ -9,9 +9,11 @@ export async function viteStart(context: Context): Promise { const { applyHook, command, commandArgs } = context; const configArr = context.getWebpackConfig(); + const devConfig = configArr.length > 0 ? wp2vite(context) : {}; await applyHook(`before.${command}.load`, { args: commandArgs, webpackConfig: configArr, + viteConfig: devConfig, }); if (!configArr.length) { @@ -20,8 +22,6 @@ export async function viteStart(context: Context): Promise { return; } - const devConfig = wp2vite(context); - await applyHook(`before.${command}.run`, { args: commandArgs, config: devConfig, diff --git a/yarn.lock b/yarn.lock index f3750dabb4..68ba2a273f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4893,7 +4893,7 @@ build-scripts-config@^3.0.0: webpack-filter-warnings-plugin "^1.2.1" webpack-simple-progress-plugin "0.0.4" -build-scripts@^1.0.0, build-scripts@^1.0.1, build-scripts@^1.1.0: +build-scripts@^1.0.0, build-scripts@^1.0.1, build-scripts@^1.1.0, build-scripts@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/build-scripts/-/build-scripts-1.2.1.tgz#626bf4343c425124320bea0189bfdafa6501280e" integrity sha512-qumrwcAO1SwhN23Nn98gkHCmZw+PZ4nGjQdBp2vfQSq8/rElqXBP4SlgfbX7RP0ZfRZdUXeUgfRegco48XnJ6g== @@ -6814,11 +6814,6 @@ es-abstract@^1.17.2, es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19 string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-module-lexer@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e" - integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA== - es-module-lexer@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.7.1.tgz#c2c8e0f46f2df06274cdaf0dd3f3b33e0a0b267d" From 7f4faad46ec72f41c2cdf7d48b324eb69f6c5328 Mon Sep 17 00:00:00 2001 From: dashdotdawn Date: Thu, 6 Jan 2022 15:58:06 +0800 Subject: [PATCH 03/20] fix: vite index.html content-type (#5110) --- packages/vite-service/src/plugins/html.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vite-service/src/plugins/html.ts b/packages/vite-service/src/plugins/html.ts index 673cb00aed..77b7d89bb1 100644 --- a/packages/vite-service/src/plugins/html.ts +++ b/packages/vite-service/src/plugins/html.ts @@ -84,6 +84,7 @@ export const htmlPlugin = ({ filename, template, entry, rootDir, templateParamet if (req.url === `/${filename}`) { try { + res.setHeader('Content-Type','text/html'); res.end(await server.transformIndexHtml(req.url, html)); } catch (e) { return next(e); From 723a8b7b25398381dc58fd8bad2e75f2afabeb90 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Tue, 11 Jan 2022 10:36:40 +0800 Subject: [PATCH 04/20] feat: SSR in mode vite ssr (#5045) * feat: vite ssr * chore: fix unexpected remove * fix: ts type * feat: ssr build in vite mode * chore: optimize code * fix: optimize code * fix: ssr in dev * chore: version and changelog * feat: support redirect url * fix: vite ssr * chore: optimize code * chore: optimize code * chore: update lock file * fix: noExternal with miniapp-history * feat: support ssg * feat: isServer * chore: comment * chore: rename plugin * chore: comment * fix: import env file * chore: changelog * chore: ts lint * fix: env path --- examples/basic-ssr/src/models/user.ts | 2 +- examples/basic-ssr/src/store.ts | 2 +- examples/basic-vite-ssr/build.json | 4 + examples/basic-vite-ssr/package.json | 17 +++++ examples/basic-vite-ssr/public/index.html | 11 +++ examples/basic-vite-ssr/src/Layout/index.tsx | 8 ++ examples/basic-vite-ssr/src/app.tsx | 13 ++++ examples/basic-vite-ssr/src/global.scss | 11 +++ .../src/pages/Dashboard/Page1/index.tsx | 5 ++ .../src/pages/Dashboard/Page2/index.tsx | 5 ++ .../src/pages/Dashboard/index.tsx | 8 ++ .../basic-vite-ssr/src/pages/Home/index.css | 75 +++++++++++++++++++ .../basic-vite-ssr/src/pages/Home/index.tsx | 39 ++++++++++ examples/basic-vite-ssr/src/routes.ts | 39 ++++++++++ examples/basic-vite-ssr/src/typings.d.ts | 4 + examples/basic-vite-ssr/tsconfig.json | 38 ++++++++++ examples/basic-vite/tsconfig.json | 6 -- packages/build-app-templates/CHANGELOG.md | 4 + packages/build-app-templates/package.json | 4 +- .../src/templates/react/runApp.ts.ejs | 3 +- packages/create-app-shared/CHANGELOG.md | 4 + packages/create-app-shared/package.json | 7 +- packages/create-app-shared/src/web/history.ts | 3 +- packages/icejs/package.json | 4 +- packages/plugin-ice-ssr/CHANGELOG.md | 4 + packages/plugin-ice-ssr/package.json | 10 ++- packages/plugin-ice-ssr/src/env.ts | 2 + packages/plugin-ice-ssr/src/index.ts | 59 ++++++++------- .../plugin-ice-ssr/src/renderPages.ts.ejs | 13 +++- .../plugin-ice-ssr/src/replaceHtmlContent.ts | 13 ++++ packages/plugin-ice-ssr/src/server.ts.ejs | 18 +++-- packages/plugin-ice-ssr/src/vite/ssrBuild.ts | 65 ++++++++++++++++ .../plugin-ice-ssr/src/vite/ssrDevHandler.ts | 54 +++++++++++++ packages/plugin-ice-ssr/src/vite/ssrPlugin.ts | 48 ++++++++++++ packages/plugin-react-app/CHANGELOG.md | 1 + packages/plugin-react-app/package.json | 3 +- packages/plugin-react-app/src/runtime.tsx | 3 +- packages/plugin-router/CHANGELOG.md | 4 + packages/plugin-router/package.json | 3 +- packages/plugin-router/src/runtime.tsx | 3 +- packages/plugin-router/src/runtime/Router.tsx | 3 +- packages/react-app-renderer/CHANGELOG.md | 4 + packages/react-app-renderer/package.json | 2 +- packages/react-app-renderer/src/renderer.tsx | 3 +- packages/react-app-renderer/src/server.tsx | 30 +++++--- packages/runtime/CHANGELOG.md | 4 + packages/runtime/package.json | 2 +- packages/runtime/src/env.ts | 17 +++++ packages/runtime/src/index.ts | 4 +- packages/vite-service/CHANGELOG.md | 4 +- packages/vite-service/src/build.ts | 5 +- packages/vite-service/src/plugins/html.ts | 51 ++++++++----- packages/vite-service/src/wp2vite/index.ts | 16 +++- test/basic-vite-ssr.test.ts | 28 +++++++ yarn.lock | 43 +++++++++-- 55 files changed, 728 insertions(+), 107 deletions(-) create mode 100644 examples/basic-vite-ssr/build.json create mode 100644 examples/basic-vite-ssr/package.json create mode 100644 examples/basic-vite-ssr/public/index.html create mode 100644 examples/basic-vite-ssr/src/Layout/index.tsx create mode 100644 examples/basic-vite-ssr/src/app.tsx create mode 100644 examples/basic-vite-ssr/src/global.scss create mode 100644 examples/basic-vite-ssr/src/pages/Dashboard/Page1/index.tsx create mode 100644 examples/basic-vite-ssr/src/pages/Dashboard/Page2/index.tsx create mode 100644 examples/basic-vite-ssr/src/pages/Dashboard/index.tsx create mode 100644 examples/basic-vite-ssr/src/pages/Home/index.css create mode 100644 examples/basic-vite-ssr/src/pages/Home/index.tsx create mode 100644 examples/basic-vite-ssr/src/routes.ts create mode 100644 examples/basic-vite-ssr/src/typings.d.ts create mode 100644 examples/basic-vite-ssr/tsconfig.json create mode 100644 packages/plugin-ice-ssr/src/env.ts create mode 100644 packages/plugin-ice-ssr/src/replaceHtmlContent.ts create mode 100644 packages/plugin-ice-ssr/src/vite/ssrBuild.ts create mode 100644 packages/plugin-ice-ssr/src/vite/ssrDevHandler.ts create mode 100644 packages/plugin-ice-ssr/src/vite/ssrPlugin.ts create mode 100644 packages/runtime/src/env.ts create mode 100644 test/basic-vite-ssr.test.ts diff --git a/examples/basic-ssr/src/models/user.ts b/examples/basic-ssr/src/models/user.ts index 7c3cf8bdf7..860e27ea10 100644 --- a/examples/basic-ssr/src/models/user.ts +++ b/examples/basic-ssr/src/models/user.ts @@ -24,4 +24,4 @@ export default { }); }, }), -}; +}; \ No newline at end of file diff --git a/examples/basic-ssr/src/store.ts b/examples/basic-ssr/src/store.ts index 34eafd1ac3..ac331130d1 100644 --- a/examples/basic-ssr/src/store.ts +++ b/examples/basic-ssr/src/store.ts @@ -1,4 +1,4 @@ import { createStore } from 'ice'; import user from './models/user'; -export default createStore({ user }); +export default createStore({ user }); \ No newline at end of file diff --git a/examples/basic-vite-ssr/build.json b/examples/basic-vite-ssr/build.json new file mode 100644 index 0000000000..2c76e33c95 --- /dev/null +++ b/examples/basic-vite-ssr/build.json @@ -0,0 +1,4 @@ +{ + "ssr": true, + "vite": true +} diff --git a/examples/basic-vite-ssr/package.json b/examples/basic-vite-ssr/package.json new file mode 100644 index 0000000000..4b47e7a16a --- /dev/null +++ b/examples/basic-vite-ssr/package.json @@ -0,0 +1,17 @@ +{ + "name": "basic-vite-ssr", + "version": "0.0.1", + "scripts": { + "start": "../../packages/icejs/bin/ice-cli.js start", + "build": "../../packages/icejs/bin/ice-cli.js build" + }, + "dependencies": { + "react": "^17.0.0", + "react-dom": "^17.0.0" + }, + "devDependencies": { + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0" + }, + "license": "MIT" +} diff --git a/examples/basic-vite-ssr/public/index.html b/examples/basic-vite-ssr/public/index.html new file mode 100644 index 0000000000..2cb5736a9b --- /dev/null +++ b/examples/basic-vite-ssr/public/index.html @@ -0,0 +1,11 @@ + + + + + + Vite App + + +
+ + diff --git a/examples/basic-vite-ssr/src/Layout/index.tsx b/examples/basic-vite-ssr/src/Layout/index.tsx new file mode 100644 index 0000000000..6f5fb17743 --- /dev/null +++ b/examples/basic-vite-ssr/src/Layout/index.tsx @@ -0,0 +1,8 @@ +export default ({ children }) => { + return ( +
+ Layout + {children} +
+ ); +}; diff --git a/examples/basic-vite-ssr/src/app.tsx b/examples/basic-vite-ssr/src/app.tsx new file mode 100644 index 0000000000..bb24f952df --- /dev/null +++ b/examples/basic-vite-ssr/src/app.tsx @@ -0,0 +1,13 @@ +import { runApp, IAppConfig } from 'ice'; + +const appConfig: IAppConfig = { + app: { + rootId: 'ice-container', + errorBoundary: true, + }, + router: { + type: 'browser', + }, +}; + +runApp(appConfig); diff --git a/examples/basic-vite-ssr/src/global.scss b/examples/basic-vite-ssr/src/global.scss new file mode 100644 index 0000000000..89e57c7ccf --- /dev/null +++ b/examples/basic-vite-ssr/src/global.scss @@ -0,0 +1,11 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +} diff --git a/examples/basic-vite-ssr/src/pages/Dashboard/Page1/index.tsx b/examples/basic-vite-ssr/src/pages/Dashboard/Page1/index.tsx new file mode 100644 index 0000000000..9f9400cf8d --- /dev/null +++ b/examples/basic-vite-ssr/src/pages/Dashboard/Page1/index.tsx @@ -0,0 +1,5 @@ +export default () => { + return ( +
Page1
+ ); +}; diff --git a/examples/basic-vite-ssr/src/pages/Dashboard/Page2/index.tsx b/examples/basic-vite-ssr/src/pages/Dashboard/Page2/index.tsx new file mode 100644 index 0000000000..d255b94c78 --- /dev/null +++ b/examples/basic-vite-ssr/src/pages/Dashboard/Page2/index.tsx @@ -0,0 +1,5 @@ +export default () => { + return ( +
Page2
+ ); +}; diff --git a/examples/basic-vite-ssr/src/pages/Dashboard/index.tsx b/examples/basic-vite-ssr/src/pages/Dashboard/index.tsx new file mode 100644 index 0000000000..dc79cf911c --- /dev/null +++ b/examples/basic-vite-ssr/src/pages/Dashboard/index.tsx @@ -0,0 +1,8 @@ +export default ({ children }) => { + return ( +
+ 这是 Dashboard + {children} +
+ ); +}; diff --git a/examples/basic-vite-ssr/src/pages/Home/index.css b/examples/basic-vite-ssr/src/pages/Home/index.css new file mode 100644 index 0000000000..0e60de25db --- /dev/null +++ b/examples/basic-vite-ssr/src/pages/Home/index.css @@ -0,0 +1,75 @@ +:root { + --primay: black; + --bg-primay: white; + + --link: #2a0b0b; +} + +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +p { + margin: 20px 0px; +} + +.header { + font-size: 3rem; +} + +.body { + margin: 20px 0 10px; + font-size: 0.9rem; +} + +button { + outline: none; + border: none; + border-radius: 8px; + padding: 10px 35px; + + background: #845ec2; + color: white; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + + background: var(--bg-primay); + color: var(--primay); +} + +.App-link { + color: #61dafb; + color: var(--link); +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +button { + font-size: calc(10px + 2vmin); +} diff --git a/examples/basic-vite-ssr/src/pages/Home/index.tsx b/examples/basic-vite-ssr/src/pages/Home/index.tsx new file mode 100644 index 0000000000..38fca08326 --- /dev/null +++ b/examples/basic-vite-ssr/src/pages/Home/index.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react'; +import './index.css'; + +function App() { + const [count, setCount] = useState(0); + + return ( +
+
+
+ + +

Don't forget to install AppWorks in Your Vscode.

+

+ + Learn React + + {' | '} + + Vite Docs + +

+
+
+
+ ); +} + +export default App; diff --git a/examples/basic-vite-ssr/src/routes.ts b/examples/basic-vite-ssr/src/routes.ts new file mode 100644 index 0000000000..6a6d159c48 --- /dev/null +++ b/examples/basic-vite-ssr/src/routes.ts @@ -0,0 +1,39 @@ +import { IRouterConfig } from 'ice'; +import DashboardLayout from '@/pages/Dashboard'; +import Dashboard1 from '@/pages/Dashboard/Page1'; +import Dashboard2 from '@/pages/Dashboard/Page2'; +import Home from '@/pages/Home'; +import Layout from '@/Layout'; + +const routes: IRouterConfig[] = [ + { + path: '/', + component: Home, + exact: true + }, + { + path: '/', + component: Layout, + children: [ + { + path: '/dashboard', + component: DashboardLayout, + children: [ + { + path: '/a', + component: Dashboard1, + exact: true + }, + { + path: '/b', + component: Dashboard2, + exact: true + } + ] + }, + ] + }, + +]; + +export default routes; diff --git a/examples/basic-vite-ssr/src/typings.d.ts b/examples/basic-vite-ssr/src/typings.d.ts new file mode 100644 index 0000000000..0506fbcb48 --- /dev/null +++ b/examples/basic-vite-ssr/src/typings.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.scss' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/examples/basic-vite-ssr/tsconfig.json b/examples/basic-vite-ssr/tsconfig.json new file mode 100644 index 0000000000..d16587828d --- /dev/null +++ b/examples/basic-vite-ssr/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "react-jsx", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "allowJs": true, + "rootDir": "./", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true, + "types": ["node", "jest", "vite/client"], + "paths": { + "@/*": [ + "./src/*" + ], + "ice": [ + ".ice/index.ts" + ] + } + } +} diff --git a/examples/basic-vite/tsconfig.json b/examples/basic-vite/tsconfig.json index 66878ad370..d16587828d 100644 --- a/examples/basic-vite/tsconfig.json +++ b/examples/basic-vite/tsconfig.json @@ -32,12 +32,6 @@ ], "ice": [ ".ice/index.ts" - ], - "ice/*": [ - ".ice/pages/*" - ], - "$store": [ - "src/store1.ts" ] } } diff --git a/packages/build-app-templates/CHANGELOG.md b/packages/build-app-templates/CHANGELOG.md index c6858328a0..d1b2f219b9 100644 --- a/packages/build-app-templates/CHANGELOG.md +++ b/packages/build-app-templates/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.1 + +- [feat] support server check by `isServer` imported from `@ice/runtime` + ## 1.1.0 - [chore] use `enableRouter` replace `buildConfig.router` diff --git a/packages/build-app-templates/package.json b/packages/build-app-templates/package.json index f84b39bf60..b9cad8ef69 100644 --- a/packages/build-app-templates/package.json +++ b/packages/build-app-templates/package.json @@ -1,6 +1,6 @@ { "name": "@builder/app-templates", - "version": "1.1.0", + "version": "1.1.1", "description": "App templates for ice.js and rax-app", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -10,7 +10,7 @@ "lib": "lib" }, "dependencies": { - "create-app-shared": "^1.2.0" + "create-app-shared": "^1.2.2" }, "files": [ "lib", diff --git a/packages/build-app-templates/src/templates/react/runApp.ts.ejs b/packages/build-app-templates/src/templates/react/runApp.ts.ejs index 7a564451e7..524e9b3d18 100644 --- a/packages/build-app-templates/src/templates/react/runApp.ts.ejs +++ b/packages/build-app-templates/src/templates/react/runApp.ts.ejs @@ -10,6 +10,7 @@ import { <% } %> } from 'create-app-shared'; import reactAppRenderer, { RenderAppConfig } from 'react-app-renderer'; +import { isServer } from '@ice/runtime'; <% if (globalStyle) {%> // eslint-disable-next-line @@ -52,7 +53,7 @@ export function runApp(appConfig?: IAppConfig) { // set History before GID initHistory && initHistory(appConfig as any); <% } %> - if (process.env.__IS_SERVER__) return; + if (isServer) return; reactAppRenderer({ appConfig: appConfig as RenderAppConfig, buildConfig, diff --git a/packages/create-app-shared/CHANGELOG.md b/packages/create-app-shared/CHANGELOG.md index 4c9da999dd..777e4d37d2 100644 --- a/packages/create-app-shared/CHANGELOG.md +++ b/packages/create-app-shared/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.2 + +- [feat] support server check by `isServer` imported from `@ice/runtime` + ## 1.2.1 - [fix] miniapp exports field diff --git a/packages/create-app-shared/package.json b/packages/create-app-shared/package.json index f75e899971..49993fdaf3 100644 --- a/packages/create-app-shared/package.json +++ b/packages/create-app-shared/package.json @@ -1,14 +1,15 @@ { "name": "create-app-shared", - "version": "1.2.1", + "version": "1.2.2", "description": "", "author": "ice-admin@alibaba-inc.com", "homepage": "https://github.com/alibaba/ice#readme", "license": "MIT", "main": "lib/index.js", "dependencies": { + "@ice/runtime": "^0.1.1", "history": "^4.9.0", - "miniapp-history": "^0.1.0", + "miniapp-history": "^0.1.6", "query-string": "^6.13.1", "universal-env": "^3.0.0" }, @@ -49,4 +50,4 @@ "@types/rax": "^1.0.6", "react": "^17.0.2" } -} +} \ No newline at end of file diff --git a/packages/create-app-shared/src/web/history.ts b/packages/create-app-shared/src/web/history.ts index e07fa32169..fd0d8d04a3 100644 --- a/packages/create-app-shared/src/web/history.ts +++ b/packages/create-app-shared/src/web/history.ts @@ -1,11 +1,12 @@ import { createBrowserHistory, createHashHistory, createMemoryHistory, History } from 'history'; +import { isServer } from '@ice/runtime'; import type { CreateHistory, InitHistory } from '../createInitHistory'; import createInitHistory from '../createInitHistory'; import { setHistory } from '../storage'; const createHistory: CreateHistory = ({ type, basename, location }) => { let history: History; - if (process.env.__IS_SERVER__) { + if (isServer) { history = createMemoryHistory(); (history as any).location = location; } else if (type === 'hash') { diff --git a/packages/icejs/package.json b/packages/icejs/package.json index 38f7969f89..db1b6b7818 100644 --- a/packages/icejs/package.json +++ b/packages/icejs/package.json @@ -31,8 +31,8 @@ "build-plugin-ice-logger": "2.0.0", "build-plugin-ice-mpa": "2.1.0", "build-plugin-ice-request": "2.0.0", - "build-plugin-ice-router": "2.1.1", - "build-plugin-ice-ssr": "3.0.6", + "build-plugin-ice-router": "2.1.2", + "build-plugin-ice-ssr": "3.1.0", "build-plugin-ice-store": "2.0.6", "build-plugin-react-app": "2.2.0", "build-plugin-pwa": "1.1.0", diff --git a/packages/plugin-ice-ssr/CHANGELOG.md b/packages/plugin-ice-ssr/CHANGELOG.md index e4b32bf2ba..dece0c9091 100644 --- a/packages/plugin-ice-ssr/CHANGELOG.md +++ b/packages/plugin-ice-ssr/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.1.0 + +- [feat] support SSR in mode Vite + ## 3.0.6 - [fix] server-side render failed in production(always render `global.__ICE_SERVER_HTML_TEMPLATE__`) diff --git a/packages/plugin-ice-ssr/package.json b/packages/plugin-ice-ssr/package.json index d3065b104c..36de0f27cb 100644 --- a/packages/plugin-ice-ssr/package.json +++ b/packages/plugin-ice-ssr/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-ice-ssr", - "version": "3.0.6", + "version": "3.1.0", "description": "ssr plugin", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -27,12 +27,18 @@ "@builder/webpack-config": "^2.0.0", "@loadable/babel-plugin": "^5.13.2", "@loadable/webpack-plugin": "^5.14.0", + "@rollup/plugin-replace": "^3.0.0", "chalk": "^4.0.0", "cheerio": "^1.0.0-rc.3", + "deepmerge": "^4.2.2", "fs-extra": "^8.1.0", "html-minifier": "^4.0.0", "parseurl": "^1.3.3", "@ice/runtime": "^0.1.0", - "query-string": "^6.13.7" + "query-string": "^6.13.7", + "vite": "^2.0.0" + }, + "devDependencies": { + "@types/connect": "^3.4.35" } } \ No newline at end of file diff --git a/packages/plugin-ice-ssr/src/env.ts b/packages/plugin-ice-ssr/src/env.ts new file mode 100644 index 0000000000..dca946e879 --- /dev/null +++ b/packages/plugin-ice-ssr/src/env.ts @@ -0,0 +1,2 @@ +// setup server env +(global as any).__IS_SERVER__ = true; \ No newline at end of file diff --git a/packages/plugin-ice-ssr/src/index.ts b/packages/plugin-ice-ssr/src/index.ts index 1e4a58f5d6..707504310b 100644 --- a/packages/plugin-ice-ssr/src/index.ts +++ b/packages/plugin-ice-ssr/src/index.ts @@ -1,28 +1,54 @@ import * as path from 'path'; import * as fse from 'fs-extra'; -import { minify } from 'html-minifier'; import LoadablePlugin from '@loadable/webpack-plugin'; import getWebpackConfig from '@builder/webpack-config'; import { formatPath } from '@builder/app-helpers'; +import type { IPlugin } from 'build-scripts'; import generateStaticPages from './generateStaticPages'; +import vitePluginSSR from './vite/ssrPlugin'; +import ssrBuild from './vite/ssrBuild'; +import replaceHtmlContent from './replaceHtmlContent'; -const plugin = async (api): Promise => { +const plugin: IPlugin = async (api): Promise => { const { context, registerTask, getValue, onGetWebpackConfig, onHook, log, applyMethod, modifyUserConfig } = api; const { rootDir, command, webpack, commandArgs, userConfig } = context; const { outputDir, ssr } = userConfig; - const TEMP_PATH = getValue('TEMP_PATH'); + const TEMP_PATH = getValue('TEMP_PATH'); // Note: Compatible plugins to modify configuration - const buildDir = path.join(rootDir, outputDir); + const buildDir = path.join(rootDir, outputDir as string); const serverDir = path.join(buildDir, 'server'); const serverFilename = 'index.js'; const serverFilePath = path.join(serverDir, serverFilename); // render server entry - const templatePath = path.join(__dirname, '../src/server.ts.ejs'); + const templatePath = path.join(__dirname, './server.ts.ejs'); const ssrEntry = path.join(TEMP_PATH, 'plugins/ssr/server.ts'); + const ssgEntry = path.join(TEMP_PATH, 'plugins/ssr/renderPage.ts'); const routesFileExists = Boolean(applyMethod('getSourceFile', 'src/routes', rootDir)); - applyMethod('addRenderFile', templatePath, ssrEntry, { outputDir, routesPath: routesFileExists ? '@' : '../..' }); + applyMethod('addRenderFile', path.join(__dirname, '../src/env.ts'), path.join(TEMP_PATH, 'plugins/ssr/env.ts')); + const renderProps = { + outputDir, + routesPath: routesFileExists ? '@' : '../..', + disableLoadable: !!userConfig.vite, + }; + applyMethod('addRenderFile', templatePath, ssrEntry, renderProps); + if (ssr === 'static') { + applyMethod('addRenderFile', path.join(__dirname, './renderPages.ts.ejs'), ssgEntry, renderProps); + } + + if (userConfig.vite) { + modifyUserConfig('vite.plugins', [vitePluginSSR(ssrEntry)], { deepmerge: true }); + onHook('after.build.compile', async ({ config }) => { + // dev 阶段直接使用 ssrLoadModule 实时加载并执行 + // build 服务未实现类似 ssrLoadModule 的能力,需要将 server 源码进行打包 + await ssrBuild(config, { ssrEntry, ssgEntry, ssr: ssr as string }); + if (ssr === 'static') { + await generateStaticPages(buildDir, serverFilePath); + } + }); + return; + } const mode = command === 'start' ? 'development' : 'production'; @@ -184,20 +210,8 @@ const plugin = async (api): Promise => { if (command === 'build' && ssr === 'static') { // SSG, pre-render page in production - const ssgTemplatePath = path.join(__dirname, './renderPages.ts.ejs'); - const ssgEntryPath = path.join(TEMP_PATH, 'plugins/ssr/renderPages.ts'); const ssgBundlePath = path.join(serverDir, 'renderPages.js'); - applyMethod( - 'addRenderFile', - ssgTemplatePath, - ssgEntryPath, - { - outputDir, - routesPath: routesFileExists ? '@' : '.', - } - ); - - config.entry('renderPages').add(ssgEntryPath); + config.entry('renderPages').add(ssgEntry); onHook('after.build.compile', async () => { await generateStaticPages(buildDir, ssgBundlePath); @@ -208,12 +222,7 @@ const plugin = async (api): Promise => { onHook(`after.${command}.compile`, () => { const htmlFilePath = path.join(buildDir, 'index.html'); - const bundle = fse.readFileSync(serverFilePath, 'utf-8'); - const html = fse.readFileSync(htmlFilePath, 'utf-8'); - const minifiedHtml = minify(html, { collapseWhitespace: true, quoteCharacter: '\'' }).replace(/`/g, '`'); - // `"` in the regulation expression is to be compatible with the minifier(such as terser) - const newBundle = bundle.replace(/['"]global.__ICE_SERVER_HTML_TEMPLATE__['"]/, `\`${minifiedHtml}\``); - fse.writeFileSync(serverFilePath, newBundle, 'utf-8'); + replaceHtmlContent(htmlFilePath, serverFilePath); }); }; diff --git a/packages/plugin-ice-ssr/src/renderPages.ts.ejs b/packages/plugin-ice-ssr/src/renderPages.ts.ejs index f79279c2fb..9191bd1498 100644 --- a/packages/plugin-ice-ssr/src/renderPages.ts.ejs +++ b/packages/plugin-ice-ssr/src/renderPages.ts.ejs @@ -1,14 +1,25 @@ import '@/app'; import { join } from 'path'; +<% if (enableRouter) { %> import routes from '<%- routesPath %>/routes'; +<% } %> +<% if (!disableLoadable) { %> import loadable from '@loadable/component'; +<% } %> import { pathToRegexp } from '@ice/runtime'; import { renderStatic } from './server'; +<% if (!enableRouter) { %> +const routes = []; +<% } %> export default async function ssgRender(options) { const { htmlTemplate } = options; + <% if (!disableLoadable) { %> const loadableStatsPath = join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json'); const buildConfig = { loadableStatsPath }; + <% } else { %> + const buildConfig = {}; + <% } %> const htmlTemplateContent = htmlTemplate || 'global.__ICE_SERVER_HTML_TEMPLATE__'; const pagesData = []; const flatRoutes = await getFlatRoutes(routes || []); @@ -17,7 +28,7 @@ export default async function ssgRender(options) { const { path = '', getInitialProps, ...rest } = flatRoute; const keys = []; - pathToRegexp(path, keys); + (pathToRegexp.default || pathToRegexp)(path, keys); if (keys.length > 0) { // don't render and export static page when the route is dynamic, e.g.: /news/:id, /* continue; diff --git a/packages/plugin-ice-ssr/src/replaceHtmlContent.ts b/packages/plugin-ice-ssr/src/replaceHtmlContent.ts new file mode 100644 index 0000000000..2a09150706 --- /dev/null +++ b/packages/plugin-ice-ssr/src/replaceHtmlContent.ts @@ -0,0 +1,13 @@ +import * as fse from 'fs-extra'; +import { minify } from 'html-minifier'; + +const replaceHtmlContent = (htmlFilePath: string, serverBundlePath: string) => { + const bundle = fse.readFileSync(serverBundlePath, 'utf-8'); + const html = fse.readFileSync(htmlFilePath, 'utf-8'); + const minifiedHtml = minify(html, { collapseWhitespace: true, quoteCharacter: '\'' }).replace(/`/g, '`'); + // `"` in the regulation expression is to be compatible with the minifier(such as terser) + const newBundle = bundle.replace(/['"]global.__ICE_SERVER_HTML_TEMPLATE__['"]/, `\`${minifiedHtml}\``); + fse.writeFileSync(serverBundlePath, newBundle, 'utf-8'); +}; + +export default replaceHtmlContent; diff --git a/packages/plugin-ice-ssr/src/server.ts.ejs b/packages/plugin-ice-ssr/src/server.ts.ejs index 224b8ebb56..e692f15436 100644 --- a/packages/plugin-ice-ssr/src/server.ts.ejs +++ b/packages/plugin-ice-ssr/src/server.ts.ejs @@ -1,3 +1,4 @@ +import './env'; import '@/app'; import { join } from 'path'; import { pathToRegexp } from '@ice/runtime'; @@ -19,9 +20,8 @@ import routes from '<%- routesPath %>/routes'; const routes = []; <% } %> import loadable from '@loadable/component'; - -const chalk = require('chalk'); -const parseurl = require('parseurl'); +import * as chalk from 'chalk'; +import parseurl from 'parseurl'; const { createBaseApp, initAppLifeCycles } = app; @@ -56,7 +56,7 @@ const serverRender = async (context: ServerContext, opts = {}) => { publicPath, } = options; - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === 'development' && loadableStatsPath !== false) { // loadable-stats.json will be generated to the build dir in development loadableStatsPath = join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json'); } @@ -91,7 +91,7 @@ const serverRender = async (context: ServerContext, opts = {}) => { } const pageData = pagesData.find(({ path, exact: end = false, strict = false, sensitive = false }) => { try { - const regexp = pathToRegexp(path, [], { end, strict, sensitive }); + const regexp = (pathToRegexp.default || pathToRegexp)(path, [], { end, strict, sensitive }); return regexp.test(initialContext.pathname); } catch (e) { return false; @@ -201,9 +201,11 @@ export function generateHtml({ window.__ICE_PAGE_PROPS__=${JSON.stringify(pageInitialProps)}; `); // inject assets - $('head').append(`${loadableComponentExtractor.getLinkTags()}`); - $('head').append(`${loadableComponentExtractor.getStyleTags()}`); - $('body').append(`${loadableComponentExtractor.getScriptTags()}`); + if (loadableComponentExtractor) { + $('head').append(`${loadableComponentExtractor.getLinkTags()}`); + $('head').append(`${loadableComponentExtractor.getStyleTags()}`); + $('body').append(`${loadableComponentExtractor.getScriptTags()}`); + } // inject tags to header $('head').append(`${helmet.title.toString()}`); $('head').append(`${helmet.meta.toString()}`); diff --git a/packages/plugin-ice-ssr/src/vite/ssrBuild.ts b/packages/plugin-ice-ssr/src/vite/ssrBuild.ts new file mode 100644 index 0000000000..9a2d91e1d6 --- /dev/null +++ b/packages/plugin-ice-ssr/src/vite/ssrBuild.ts @@ -0,0 +1,65 @@ +import * as path from 'path'; +import { build } from 'vite'; +import type { InlineConfig, Plugin } from 'vite'; +import { all } from 'deepmerge'; +import replaceHtmlContent from '../replaceHtmlContent'; + +interface Options { + ssrEntry: string; + ssgEntry: string; + ssr: string; +} +// simple array merge for config merge +const arrayMerge = (destinationArray: any[], sourceArray: any[]) => { + return [...(destinationArray || []), ...(sourceArray || [])]; +}; + +const ssrBuild = async (prodConfig: InlineConfig, buildOptions: Options): Promise=> { + const { ssrEntry, ssr, ssgEntry } = buildOptions; + const distDir = + prodConfig.build?.outDir ?? path.resolve(process.cwd(), 'build'); + const entry = ssr === 'static' ? ssgEntry : ssrEntry; + const buildConfig = all( + [prodConfig, { + // No need to copy public files to SSR directory + publicDir: false, + build: { + minify: false, + outDir: path.resolve(distDir, 'server'), + ssr: entry, + emptyOutDir: false, + rollupOptions: { + input: { + index: entry + }, + output: { + entryFileNames: '[name].js', + chunkFileNames: '[name].js', + }, + plugins: [ + // eslint-disable-next-line global-require + require('@rollup/plugin-replace')({ + preventAssignment: true, + values: { + 'process.env.__IS_SERVER__': true, + }, + }), + ], + }, + }, + }], { arrayMerge } + ) as InlineConfig; + // filter vite-plugin-html-index for create html entry + buildConfig.plugins = buildConfig.plugins.filter((plugin) => { + const { name } = plugin as Plugin; + return name !== 'vite-plugin-html-index'; + }); + try { + await build(buildConfig); + replaceHtmlContent(path.join(prodConfig.build?.outDir as string, 'index.html'), path.resolve(distDir, 'server', 'index.js')); + } catch (err) { + console.error(err); + } +}; + +export default ssrBuild; diff --git a/packages/plugin-ice-ssr/src/vite/ssrDevHandler.ts b/packages/plugin-ice-ssr/src/vite/ssrDevHandler.ts new file mode 100644 index 0000000000..630c2f0996 --- /dev/null +++ b/packages/plugin-ice-ssr/src/vite/ssrDevHandler.ts @@ -0,0 +1,54 @@ +import type { ViteDevServer } from 'vite'; +import type { NextHandleFunction } from 'connect'; + +interface SSROptions { + ssrEntry: string; +} + +const createSSRDevHandler = (server: ViteDevServer, options: SSROptions): NextHandleFunction => { + const { ssrEntry } = options; + const requestHandler: NextHandleFunction = async (req, res, next) => { + // just do some filter already known + if (req.method !== 'GET' || req.originalUrl === '/favicon.ico') { + return next(); + } + let htmlTemplate: string; + try { + // html content will be provided by vite-plugin-html + htmlTemplate = await server.transformIndexHtml(req.originalUrl, ''); + } catch (err) { + server.ssrFixStacktrace(err); + // fallback + return next(err); + } + + try { + const serverEntryPoint = await server.ssrLoadModule(ssrEntry); + const render = serverEntryPoint.default; + const url = req.url; + req.url = req.originalUrl; + const { error, html, redirectUrl } = await render({ req, res }, { htmlTemplate, loadableStatsPath: false }); + req.url = url; + if (redirectUrl) { + console.log('[SSR]', `Redirect to the new path ${redirectUrl}`); + res.writeHead(302, { + Location: redirectUrl + }); + res.end(); + } else { + if (error) { + server.ssrFixStacktrace(error as Error); + } + console.log('[SSR] ssr html content', url, req.originalUrl, html); + res.setHeader('Content-Type', 'text/html'); + res.end(html); + } + } catch (err) { + server.ssrFixStacktrace(err); + } + }; + + return requestHandler; +}; + +export default createSSRDevHandler; \ No newline at end of file diff --git a/packages/plugin-ice-ssr/src/vite/ssrPlugin.ts b/packages/plugin-ice-ssr/src/vite/ssrPlugin.ts new file mode 100644 index 0000000000..07811952bd --- /dev/null +++ b/packages/plugin-ice-ssr/src/vite/ssrPlugin.ts @@ -0,0 +1,48 @@ +import type { Plugin, UserConfig } from 'vite'; +import createSSRDevHandler from './ssrDevHandler'; + +const externalPackages = [ + // server 入口依赖 + 'cheerio', + '@loadable/server', + 'chalk', + 'parseurl', + // @ice/runtime 依赖 + 'axios', + 'path-to-regexp', + // @ice/store 依赖 + 'react-redux', + 'lodash.isfunction', +]; + +const vitePluginSSRServer = (ssrEntry: string): Plugin => { + return { + name: 'vite-plugin-ssr', + enforce: 'pre', + config() { + return { + ssr: { + // 主要由 server 入口和 noExternal 引入的 cjs 依赖,基于 cjs 规范,可以直接通过 ssrImport,如果不设置将通过 esm 方式执行导致 require / module 是 undefined 报错 + external: externalPackages, + // 设置 noExternal 逻辑,以下包均只存在 esm 产物,但未设置 type: "module",设置 noExternal 将不会通过 ssrImport 直接执行 + // TODO:后续需要以下包以相应规范产出 cjs 模块 + noExternal: ['create-app-shared', 'react-app-renderer', '@ice/runtime', '@ice/store', 'miniapp-history'], + }, + } as UserConfig; + }, + configResolved(resolvedConfig) { + resolvedConfig.resolve.alias.push({ + // universal-env 写法非标准 + // TODO:universal-env 打包出标准的 ESM 产物 + find: /universal-env$/, + replacement: '@uni/env', + }); + }, + configureServer: async (server) => { + const handler = createSSRDevHandler(server, { ssrEntry}); + return () => server.middlewares.use(handler); + }, + }; +}; + +export default vitePluginSSRServer; diff --git a/packages/plugin-react-app/CHANGELOG.md b/packages/plugin-react-app/CHANGELOG.md index 15737e081d..bd06b16a2d 100644 --- a/packages/plugin-react-app/CHANGELOG.md +++ b/packages/plugin-react-app/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.2.0 - [feat] optimize runtime when build +- [feat] support server check by `isServer` imported from `@ice/runtime` - [chore] bump version of `es-module-lexer` ## 2.1.4 diff --git a/packages/plugin-react-app/package.json b/packages/plugin-react-app/package.json index c753f7006b..810cdac506 100644 --- a/packages/plugin-react-app/package.json +++ b/packages/plugin-react-app/package.json @@ -33,7 +33,7 @@ "process": "^0.11.10", "query-loader-webpack-plugin": "^2.0.0", "query-string": "^7.0.1", - "react-app-renderer": "^3.0.2", + "react-app-renderer": "^3.1.0", "react-dev-utils": "^11.0.0", "react-refresh": "^0.10.0", "react-router": "^5.2.1", @@ -44,6 +44,7 @@ "devDependencies": { "@babel/traverse": "^7.14.5", "@babel/types": "^7.14.5", + "@ice/runtime": "^0.1.1", "build-scripts": "^1.1.0", "webpack": "^5.0.0", "webpack-chain": "^6.5.1" diff --git a/packages/plugin-react-app/src/runtime.tsx b/packages/plugin-react-app/src/runtime.tsx index 2f44a0ff8b..58a0e15a60 100644 --- a/packages/plugin-react-app/src/runtime.tsx +++ b/packages/plugin-react-app/src/runtime.tsx @@ -1,6 +1,7 @@ /* eslint-disable no-lonely-if */ import * as React from 'react'; import { useState, useEffect } from 'react'; +import { isServer } from '@ice/runtime'; import * as queryString from 'query-string'; // @ts-ignore import ErrorBoundary from '$ice/ErrorBoundary'; @@ -18,7 +19,7 @@ export default ({ appConfig, wrapperPageComponent, buildConfig, context, applyRu wrapperPageComponent(wrapperPageWithProps(applyRuntimeAPI)); } - wrapperPageComponent(process.env.__IS_SERVER__ ? wrapperPageWithSSR(context) : wrapperPageWithCSR()); + wrapperPageComponent(isServer ? wrapperPageWithSSR(context) : wrapperPageWithCSR()); wrapperPageComponent(wrapperPageWithErrorBoundary(ErrorBoundaryFallback, onErrorBoundaryHandler)); diff --git a/packages/plugin-router/CHANGELOG.md b/packages/plugin-router/CHANGELOG.md index 7387038672..96cab9b528 100644 --- a/packages/plugin-router/CHANGELOG.md +++ b/packages/plugin-router/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.1.2 + +- [feat] support server check by `isServer` imported from `@ice/runtime` + ## 2.1.1 - [fix] bump version of `@builder/pack`(^0.5.0) diff --git a/packages/plugin-router/package.json b/packages/plugin-router/package.json index 28906e4e20..7da9837cef 100644 --- a/packages/plugin-router/package.json +++ b/packages/plugin-router/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-ice-router", - "version": "2.1.1", + "version": "2.1.2", "description": "build-plugin-ice-router", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -14,6 +14,7 @@ "@builder/app-helpers": "^2.4.1", "@builder/pack": "^0.5.0", "@types/react-router-dom": "^5.1.4", + "@ice/runtime": "^0.1.1", "fs-extra": "^8.1.0", "glob": "^7.1.6", "history": "^4.10.1", diff --git a/packages/plugin-router/src/runtime.tsx b/packages/plugin-router/src/runtime.tsx index 1c0cce279f..7d2e49b684 100644 --- a/packages/plugin-router/src/runtime.tsx +++ b/packages/plugin-router/src/runtime.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { isServer } from '@ice/runtime'; // @ts-ignore import defaultRoutes from '$ice/routes'; import { IceRouter, Routes, parseRoutes } from './runtime/Router'; @@ -30,7 +31,7 @@ const module = ({ setRenderApp, appConfig, modifyRoutes, modifyRoutesComponent, if (!routerProps.history) { routerProps.history = applyRuntimeAPI('createHistory', { type: appConfigRouter.type, basename: appConfigRouter.basename }); } - if (process.env.__IS_SERVER__) { + if (isServer) { const { initialContext = {} } = context; routerProps = Object.assign({}, routerProps, { location: initialContext.location, context: initialContext }); } diff --git a/packages/plugin-router/src/runtime/Router.tsx b/packages/plugin-router/src/runtime/Router.tsx index 92d35d12ed..0953ad0874 100644 --- a/packages/plugin-router/src/runtime/Router.tsx +++ b/packages/plugin-router/src/runtime/Router.tsx @@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom'; import loadable from '@loadable/component'; +import { isServer } from '@ice/runtime'; import { RoutesProps, RouterProps } from '../types/router'; import { IRouteWrapper, IDynamicImportComponent, RouteItemProps } from '../types/base'; import { IRouterConfig } from '../types'; @@ -117,7 +118,7 @@ export function Routes({ routes, fallback }: RoutesProps) { // React does not currently support Suspense when components are being server-side rendered // process.env.__IS_SERVER__: React.RenderToString() // window.__ICE_SSR_ENABLED__: React.hydrate() - const RenderComponent = process.env.__IS_SERVER__ || (window as any).__ICE_SSR_ENABLED__ + const RenderComponent = isServer || (window as any).__ICE_SSR_ENABLED__ ? (props: RouteComponentProps) => : (props: RouteComponentProps) => { return ( diff --git a/packages/react-app-renderer/CHANGELOG.md b/packages/react-app-renderer/CHANGELOG.md index 5e23e9844d..b9979c0b2b 100644 --- a/packages/react-app-renderer/CHANGELOG.md +++ b/packages/react-app-renderer/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.0.3 + +- [fix] compatible with SSR render when loadableStatsPath and ErrorBoundary is missing + ## 3.0.2 - [fix]: `renderComponent` -> `app.renderComponent` diff --git a/packages/react-app-renderer/package.json b/packages/react-app-renderer/package.json index 0ab56408b2..384b9df155 100644 --- a/packages/react-app-renderer/package.json +++ b/packages/react-app-renderer/package.json @@ -1,6 +1,6 @@ { "name": "react-app-renderer", - "version": "3.0.2", + "version": "3.1.0", "description": "", "author": "ice-admin@alibaba-inc.com", "homepage": "https://github.com/alibaba/ice#readme", diff --git a/packages/react-app-renderer/src/renderer.tsx b/packages/react-app-renderer/src/renderer.tsx index e7bc9ee51f..47c45eade3 100644 --- a/packages/react-app-renderer/src/renderer.tsx +++ b/packages/react-app-renderer/src/renderer.tsx @@ -32,7 +32,8 @@ export function getRenderApp(runtime: RuntimeModule, options: RenderOptions) { const { ErrorBoundaryFallback, onErrorBoundaryHandler, errorBoundary, strict = false } = appConfig.app; function App() { - if (errorBoundary) { + // ErrorBoundary is missing in SSR + if (errorBoundary && ErrorBoundary) { rootApp = ( {rootApp} diff --git a/packages/react-app-renderer/src/server.tsx b/packages/react-app-renderer/src/server.tsx index d1b99becda..0633abdcbe 100644 --- a/packages/react-app-renderer/src/server.tsx +++ b/packages/react-app-renderer/src/server.tsx @@ -16,18 +16,24 @@ function renderInServer(context: Context, options: RenderOptions) { emitLifeCycles(); const App = getRenderApp(runtime, options); - - const webExtractor = new ChunkExtractor({ - statsFile: loadableStatsPath, - entrypoints: ['index'], - publicPath - }); - const jsx = webExtractor.collectChunks(); - - return { - bundleContent: ReactDOMServer.renderToString(jsx), - loadableComponentExtractor: webExtractor - }; + + if (loadableStatsPath) { + const webExtractor = new ChunkExtractor({ + statsFile: loadableStatsPath, + entrypoints: ['index'], + publicPath + }); + const jsx = webExtractor.collectChunks(); + + return { + bundleContent: ReactDOMServer.renderToString(jsx), + loadableComponentExtractor: webExtractor + }; + } else { + return { + bundleContent: ReactDOMServer.renderToString() + }; + } } export default function reactAppRendererWithSSR(context: Context, options: RenderOptions) { diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 8236984ea3..00da7bc0b5 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.2 + +- [feat] export `isServer` + ## 0.1.1 - [feat] add `path-to-regexp@1.x` dependency diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 7bad6be6ad..003a8443b8 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@ice/runtime", - "version": "0.1.1", + "version": "0.1.2", "description": "runtime dependencies for plugin runtime", "main": "lib/index.js", "scripts": { diff --git a/packages/runtime/src/env.ts b/packages/runtime/src/env.ts new file mode 100644 index 0000000000..030cc5f3ca --- /dev/null +++ b/packages/runtime/src/env.ts @@ -0,0 +1,17 @@ +interface GlobalEnv { + __IS_SERVER__: boolean; +} + +let globalPolyfill: unknown = typeof globalThis !== 'undefined' ? globalThis : null; + +if (!globalPolyfill) { + // add polyfill to globalThis + // eslint-disable-next-line no-nested-ternary + globalPolyfill = typeof window !== 'undefined' ? window : (typeof global !== 'undefined' ? global : {}); +} + +const isServer = (globalPolyfill as GlobalEnv).__IS_SERVER__; + +export { + isServer, +}; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index e098f56b1e..bc4b9f299c 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,9 +1,11 @@ import axios from 'axios'; import * as pathToRegexp from 'path-to-regexp'; import axiosUtils from './axiosUtils'; +import { isServer } from './env'; export { axios, axiosUtils, - pathToRegexp + isServer, + pathToRegexp, }; \ No newline at end of file diff --git a/packages/vite-service/CHANGELOG.md b/packages/vite-service/CHANGELOG.md index badd8a2c38..efc5b66c8f 100644 --- a/packages/vite-service/CHANGELOG.md +++ b/packages/vite-service/CHANGELOG.md @@ -1,7 +1,9 @@ # CHANGELOG -## 2.0.4 +## 2.1.0 +- [feat] refactor html plugin for SSR render when development +- [fix] lifecycle arguments for `after.build.compile` - [fix] pass vite config for lifecycle hooks ## 2.0.3 diff --git a/packages/vite-service/src/build.ts b/packages/vite-service/src/build.ts index 512397eaaf..8da9f1c765 100644 --- a/packages/vite-service/src/build.ts +++ b/packages/vite-service/src/build.ts @@ -25,7 +25,10 @@ export async function viteBuild(context: any): Promise { try { await build(prodConfig); - context.applyHook('after.build.compile'); + context.applyHook('after.build.compile', { + args: commandArgs, + config: prodConfig, + }); } catch (err) { console.error('CONFIG', chalk.red('Failed to load vite config.')); throw err; diff --git a/packages/vite-service/src/plugins/html.ts b/packages/vite-service/src/plugins/html.ts index 77b7d89bb1..9d7d229d37 100644 --- a/packages/vite-service/src/plugins/html.ts +++ b/packages/vite-service/src/plugins/html.ts @@ -33,9 +33,11 @@ interface Option { entry: string rootDir: string templateParameters?: object + ssr?: boolean; + command?: string; } -export const htmlPlugin = ({ filename, template, entry, rootDir, templateParameters = {} }: Option): Plugin => { +export const htmlPlugin = ({ filename, template, entry, rootDir, templateParameters = {}, ssr, command }: Option): Plugin => { const pageName = filename.replace('.html', ''); const getEntry = () => { @@ -55,7 +57,8 @@ export const htmlPlugin = ({ filename, template, entry, rootDir, templateParamet // vite will get relative path by `path.posix.relative(config.root, id)` // path.posix.relative will get error path when pass relative path of index html const absoluteHtmlPath = formatPath(path.join(rootDir, filename)); - return { + + const plugin: Plugin = { name: `vite-plugin-html-${pageName}`, enforce: 'pre', config(cfg) { @@ -76,24 +79,34 @@ export const htmlPlugin = ({ filename, template, entry, rootDir, templateParamet return null; }, configureServer(server: ViteDevServer) { - return () => { - server.middlewares.use(async (req, res, next) => { - if (!req.url?.endsWith('.html') && req.url !== '/') { - return next(); - } - - if (req.url === `/${filename}`) { - try { - res.setHeader('Content-Type','text/html'); - res.end(await server.transformIndexHtml(req.url, html)); - } catch (e) { - return next(e); + if (!ssr) { + return () => { + server.middlewares.use(async (req, res, next) => { + if (!req.url?.endsWith('.html') && req.url !== '/') { + return next(); } - } - - next(); - }); - }; + + if (req.url === `/${filename}`) { + try { + res.setHeader('Content-Type','text/html'); + res.end(await server.transformIndexHtml(req.url, html)); + } catch (e) { + return next(e); + } + } + + next(); + }); + }; + } } }; + // ssr 在 dev 阶段由中间件进行 html 返回 + if (ssr && command === 'start') { + plugin.transformIndexHtml = () => { + return html; + }; + } + + return plugin; }; diff --git a/packages/vite-service/src/wp2vite/index.ts b/packages/vite-service/src/wp2vite/index.ts index a5c8b4257d..4a9a7262bf 100644 --- a/packages/vite-service/src/wp2vite/index.ts +++ b/packages/vite-service/src/wp2vite/index.ts @@ -42,7 +42,7 @@ const arrayMerge = (destinationArray: any[], sourceArray: any[]) => { const isBuild = (command: string) => command === 'build'; const getHtmlPlugin = (context: Context) => { - const { getValue, userConfig, rootDir } = context; + const { getValue, userConfig, rootDir, command } = context; type Opt = { template: string filename: string @@ -54,13 +54,16 @@ const getHtmlPlugin = (context: Context) => { } const isMpa = userConfig.mpa as boolean; + const ssr = userConfig.ssr as boolean; if (!isMpa) { return htmlPlugin({ entry: userConfig.entry as string, // webpack entry template: path.resolve(rootDir, 'public', 'index.html'), filename: 'index.html', - rootDir + rootDir, + ssr, + command, }); } @@ -80,14 +83,18 @@ const getHtmlPlugin = (context: Context) => { return htmlPlugin({ ...singlePage, entry: entries[entryName][0], // webpack entry - rootDir + rootDir, + ssr, + command, }); } return htmlPlugin({ ...singlePage, entry: entries[entryName][0], // webpack entry - rootDir + rootDir, + ssr, + command, }); }); @@ -223,6 +230,7 @@ export const wp2vite = (context: Context): InlineConfig => { if (!isBuild(command)) { return all([ { + mode: 'development', optimizeDeps: { entries: getAnalysisEntries(), // vite 无法分析 link 的依赖,需要手动加入以下依赖,防止 ice 维护时报错 diff --git a/test/basic-vite-ssr.test.ts b/test/basic-vite-ssr.test.ts new file mode 100644 index 0000000000..25aa4f71d9 --- /dev/null +++ b/test/basic-vite-ssr.test.ts @@ -0,0 +1,28 @@ +import * as path from 'path'; +import * as cheerio from 'cheerio'; +import executeCommand from './utils/executeCommand'; + +const example = 'basic-vite-ssr'; + +executeCommand('npm run build', path.join(process.cwd(), 'examples/basic-vite-ssr')); + +describe(`build ${example}`, () => { + test('/', async () => { + const serverRender = require(path.join(process.cwd(), 'examples', example, 'build/server/index.js')); + const url = '/'; + const req = { + url + }; + + const res = {}; + const ctx = { req, res }; + const { html } = await serverRender.default({ + ctx, + pathname: url, + loadableStatsPath: false + }); + expect(html).toContain('window.__ICE_SSR_ENABLED__=true'); + expect(html).toContain('Click me :'); + expect(html).toContain('Vite Docs'); + }); +}) diff --git a/yarn.lock b/yarn.lock index 68ba2a273f..282a1f1d4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3067,6 +3067,23 @@ dependencies: redux "^4.0.5" +"@rollup/plugin-replace@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz#3a4c9665d4e7a4ce2c360cf021232784892f3fac" + integrity sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@rollup/pluginutils@^4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" @@ -3156,7 +3173,7 @@ "@types/express-serve-static-core" "*" "@types/node" "*" -"@types/connect@*": +"@types/connect@*", "@types/connect@^3.4.35": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== @@ -3207,6 +3224,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.26" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz#5d9a8eeecb9d5f9d7fc1d85f541512a84638ae88" @@ -7241,6 +7263,11 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + estree-walker@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -11400,10 +11427,10 @@ miniapp-builder-shared@^0.2.2: execa "^5.0.0" fs-extra "^8.0.1" -miniapp-history@^0.1.0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/miniapp-history/-/miniapp-history-0.1.5.tgz#13da3e156308f97681174ade746799737c8f83b3" - integrity sha512-dvuoJmPc/X7NDv9IZ3LmifIdhIRWe9X6efoDsaC/9qoL2f0I0eXSk2rslp/xR2lbjihxlm5sA5WJ94obOISbdA== +miniapp-history@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/miniapp-history/-/miniapp-history-0.1.6.tgz#da893790a87a9f92c137b32bc2a90d45ad730f97" + integrity sha512-oNq4t5bhCwG2ryRkFIWLgchX/18Z6fdBdh1BetfkalChHN/zDypgoZuqA6Oilj+b/Jsx6j26CKPvMmnpBjD3sQ== dependencies: universal-env "^3.0.0" @@ -16723,9 +16750,9 @@ vite-plugin-style-import@1.1.1: magic-string "^0.25.7" vite@^2.0.0, vite@^2.3.4, vite@^2.4.2, vite@^2.4.3, vite@^2.5.0, vite@^2.5.3, vite@^2.5.6, vite@^2.6.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.7.1.tgz#be50ad13214290ecbebbe5ad389ed423cb5f137e" - integrity sha512-TDXXhcu5lyQ6uosK4ZWaOyB4VzOiizk0biitRzDzaEtgSUi8rVYPc4k1xgOjLSf0OuceDJmojFKXHOX9DB1WuQ== + version "2.7.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.7.2.tgz#f9937114cf2e730a7e2e4c4f8c26ed0ed1c3bb6b" + integrity sha512-wMffVVdKZRZP/HwW3yttKL8X+IJePz7bUcnGm0vqljffpVwHpjWC3duZtJQHAGvy+wrTjmwU7vkULpZ1dVXY6w== dependencies: esbuild "^0.13.12" postcss "^8.3.11" From a111afa1ebf08845870426cb5a3d91fbc6c46bea Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Tue, 11 Jan 2022 11:40:56 +0800 Subject: [PATCH 05/20] fix: use process.env.__IS_SERVER__ in SSR (#5119) * chore: optimize code * fix: version * fix: env --- packages/build-app-templates/CHANGELOG.md | 4 ---- packages/build-app-templates/package.json | 4 ++-- .../src/templates/react/runApp.ts.ejs | 4 +--- packages/create-app-shared/CHANGELOG.md | 4 ---- packages/create-app-shared/package.json | 2 +- packages/create-app-shared/src/web/history.ts | 3 +-- packages/icejs/package.json | 2 +- packages/plugin-ice-ssr/src/env.ts | 2 +- packages/plugin-ice-ssr/src/index.ts | 2 ++ packages/plugin-ice-ssr/src/server.ts.ejs | 2 ++ packages/plugin-react-app/CHANGELOG.md | 1 - packages/plugin-react-app/src/runtime.tsx | 3 +-- packages/plugin-router/CHANGELOG.md | 4 ---- packages/plugin-router/package.json | 2 +- packages/plugin-router/src/runtime.tsx | 3 +-- packages/plugin-router/src/runtime/Router.tsx | 3 +-- packages/runtime/CHANGELOG.md | 8 -------- packages/runtime/src/env.ts | 17 ----------------- packages/runtime/src/index.ts | 2 -- 19 files changed, 15 insertions(+), 57 deletions(-) delete mode 100644 packages/runtime/src/env.ts diff --git a/packages/build-app-templates/CHANGELOG.md b/packages/build-app-templates/CHANGELOG.md index d1b2f219b9..c6858328a0 100644 --- a/packages/build-app-templates/CHANGELOG.md +++ b/packages/build-app-templates/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## 1.1.1 - -- [feat] support server check by `isServer` imported from `@ice/runtime` - ## 1.1.0 - [chore] use `enableRouter` replace `buildConfig.router` diff --git a/packages/build-app-templates/package.json b/packages/build-app-templates/package.json index b9cad8ef69..469ad2fe0d 100644 --- a/packages/build-app-templates/package.json +++ b/packages/build-app-templates/package.json @@ -1,6 +1,6 @@ { "name": "@builder/app-templates", - "version": "1.1.1", + "version": "1.1.0", "description": "App templates for ice.js and rax-app", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -10,7 +10,7 @@ "lib": "lib" }, "dependencies": { - "create-app-shared": "^1.2.2" + "create-app-shared": "^1.2.1" }, "files": [ "lib", diff --git a/packages/build-app-templates/src/templates/react/runApp.ts.ejs b/packages/build-app-templates/src/templates/react/runApp.ts.ejs index 524e9b3d18..44d173179d 100644 --- a/packages/build-app-templates/src/templates/react/runApp.ts.ejs +++ b/packages/build-app-templates/src/templates/react/runApp.ts.ejs @@ -10,8 +10,6 @@ import { <% } %> } from 'create-app-shared'; import reactAppRenderer, { RenderAppConfig } from 'react-app-renderer'; -import { isServer } from '@ice/runtime'; - <% if (globalStyle) {%> // eslint-disable-next-line import '<%= globalStyle %>' @@ -53,7 +51,7 @@ export function runApp(appConfig?: IAppConfig) { // set History before GID initHistory && initHistory(appConfig as any); <% } %> - if (isServer) return; + if (process.env.__IS_SERVER__) return; reactAppRenderer({ appConfig: appConfig as RenderAppConfig, buildConfig, diff --git a/packages/create-app-shared/CHANGELOG.md b/packages/create-app-shared/CHANGELOG.md index 777e4d37d2..4c9da999dd 100644 --- a/packages/create-app-shared/CHANGELOG.md +++ b/packages/create-app-shared/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## 1.2.2 - -- [feat] support server check by `isServer` imported from `@ice/runtime` - ## 1.2.1 - [fix] miniapp exports field diff --git a/packages/create-app-shared/package.json b/packages/create-app-shared/package.json index 49993fdaf3..6dff2ea2da 100644 --- a/packages/create-app-shared/package.json +++ b/packages/create-app-shared/package.json @@ -1,6 +1,6 @@ { "name": "create-app-shared", - "version": "1.2.2", + "version": "1.2.1", "description": "", "author": "ice-admin@alibaba-inc.com", "homepage": "https://github.com/alibaba/ice#readme", diff --git a/packages/create-app-shared/src/web/history.ts b/packages/create-app-shared/src/web/history.ts index fd0d8d04a3..e07fa32169 100644 --- a/packages/create-app-shared/src/web/history.ts +++ b/packages/create-app-shared/src/web/history.ts @@ -1,12 +1,11 @@ import { createBrowserHistory, createHashHistory, createMemoryHistory, History } from 'history'; -import { isServer } from '@ice/runtime'; import type { CreateHistory, InitHistory } from '../createInitHistory'; import createInitHistory from '../createInitHistory'; import { setHistory } from '../storage'; const createHistory: CreateHistory = ({ type, basename, location }) => { let history: History; - if (isServer) { + if (process.env.__IS_SERVER__) { history = createMemoryHistory(); (history as any).location = location; } else if (type === 'hash') { diff --git a/packages/icejs/package.json b/packages/icejs/package.json index db1b6b7818..28d79b1540 100644 --- a/packages/icejs/package.json +++ b/packages/icejs/package.json @@ -31,7 +31,7 @@ "build-plugin-ice-logger": "2.0.0", "build-plugin-ice-mpa": "2.1.0", "build-plugin-ice-request": "2.0.0", - "build-plugin-ice-router": "2.1.2", + "build-plugin-ice-router": "2.1.1", "build-plugin-ice-ssr": "3.1.0", "build-plugin-ice-store": "2.0.6", "build-plugin-react-app": "2.2.0", diff --git a/packages/plugin-ice-ssr/src/env.ts b/packages/plugin-ice-ssr/src/env.ts index dca946e879..7b1452a4de 100644 --- a/packages/plugin-ice-ssr/src/env.ts +++ b/packages/plugin-ice-ssr/src/env.ts @@ -1,2 +1,2 @@ // setup server env -(global as any).__IS_SERVER__ = true; \ No newline at end of file +process.env.__IS_SERVER__ = 'true'; \ No newline at end of file diff --git a/packages/plugin-ice-ssr/src/index.ts b/packages/plugin-ice-ssr/src/index.ts index 707504310b..0b2a042606 100644 --- a/packages/plugin-ice-ssr/src/index.ts +++ b/packages/plugin-ice-ssr/src/index.ts @@ -31,6 +31,8 @@ const plugin: IPlugin = async (api): Promise => { outputDir, routesPath: routesFileExists ? '@' : '../..', disableLoadable: !!userConfig.vite, + // 仅在 vite 的 dev 模式下,需要指定 env,build 阶段 process.env.__IS_SERVER__ 会被替换成布尔值 + importEnv: !!userConfig.vite && command !== 'build', }; applyMethod('addRenderFile', templatePath, ssrEntry, renderProps); if (ssr === 'static') { diff --git a/packages/plugin-ice-ssr/src/server.ts.ejs b/packages/plugin-ice-ssr/src/server.ts.ejs index e692f15436..e091056d71 100644 --- a/packages/plugin-ice-ssr/src/server.ts.ejs +++ b/packages/plugin-ice-ssr/src/server.ts.ejs @@ -1,4 +1,6 @@ +<% if (importEnv) { %> import './env'; +<% } %> import '@/app'; import { join } from 'path'; import { pathToRegexp } from '@ice/runtime'; diff --git a/packages/plugin-react-app/CHANGELOG.md b/packages/plugin-react-app/CHANGELOG.md index bd06b16a2d..15737e081d 100644 --- a/packages/plugin-react-app/CHANGELOG.md +++ b/packages/plugin-react-app/CHANGELOG.md @@ -3,7 +3,6 @@ ## 2.2.0 - [feat] optimize runtime when build -- [feat] support server check by `isServer` imported from `@ice/runtime` - [chore] bump version of `es-module-lexer` ## 2.1.4 diff --git a/packages/plugin-react-app/src/runtime.tsx b/packages/plugin-react-app/src/runtime.tsx index 58a0e15a60..2f44a0ff8b 100644 --- a/packages/plugin-react-app/src/runtime.tsx +++ b/packages/plugin-react-app/src/runtime.tsx @@ -1,7 +1,6 @@ /* eslint-disable no-lonely-if */ import * as React from 'react'; import { useState, useEffect } from 'react'; -import { isServer } from '@ice/runtime'; import * as queryString from 'query-string'; // @ts-ignore import ErrorBoundary from '$ice/ErrorBoundary'; @@ -19,7 +18,7 @@ export default ({ appConfig, wrapperPageComponent, buildConfig, context, applyRu wrapperPageComponent(wrapperPageWithProps(applyRuntimeAPI)); } - wrapperPageComponent(isServer ? wrapperPageWithSSR(context) : wrapperPageWithCSR()); + wrapperPageComponent(process.env.__IS_SERVER__ ? wrapperPageWithSSR(context) : wrapperPageWithCSR()); wrapperPageComponent(wrapperPageWithErrorBoundary(ErrorBoundaryFallback, onErrorBoundaryHandler)); diff --git a/packages/plugin-router/CHANGELOG.md b/packages/plugin-router/CHANGELOG.md index 96cab9b528..7387038672 100644 --- a/packages/plugin-router/CHANGELOG.md +++ b/packages/plugin-router/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## 2.1.2 - -- [feat] support server check by `isServer` imported from `@ice/runtime` - ## 2.1.1 - [fix] bump version of `@builder/pack`(^0.5.0) diff --git a/packages/plugin-router/package.json b/packages/plugin-router/package.json index 7da9837cef..36d86fd28d 100644 --- a/packages/plugin-router/package.json +++ b/packages/plugin-router/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-ice-router", - "version": "2.1.2", + "version": "2.1.1", "description": "build-plugin-ice-router", "author": "ice-admin@alibaba-inc.com", "homepage": "", diff --git a/packages/plugin-router/src/runtime.tsx b/packages/plugin-router/src/runtime.tsx index 7d2e49b684..1c0cce279f 100644 --- a/packages/plugin-router/src/runtime.tsx +++ b/packages/plugin-router/src/runtime.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { isServer } from '@ice/runtime'; // @ts-ignore import defaultRoutes from '$ice/routes'; import { IceRouter, Routes, parseRoutes } from './runtime/Router'; @@ -31,7 +30,7 @@ const module = ({ setRenderApp, appConfig, modifyRoutes, modifyRoutesComponent, if (!routerProps.history) { routerProps.history = applyRuntimeAPI('createHistory', { type: appConfigRouter.type, basename: appConfigRouter.basename }); } - if (isServer) { + if (process.env.__IS_SERVER__) { const { initialContext = {} } = context; routerProps = Object.assign({}, routerProps, { location: initialContext.location, context: initialContext }); } diff --git a/packages/plugin-router/src/runtime/Router.tsx b/packages/plugin-router/src/runtime/Router.tsx index 0953ad0874..92d35d12ed 100644 --- a/packages/plugin-router/src/runtime/Router.tsx +++ b/packages/plugin-router/src/runtime/Router.tsx @@ -10,7 +10,6 @@ import { RouteComponentProps } from 'react-router-dom'; import loadable from '@loadable/component'; -import { isServer } from '@ice/runtime'; import { RoutesProps, RouterProps } from '../types/router'; import { IRouteWrapper, IDynamicImportComponent, RouteItemProps } from '../types/base'; import { IRouterConfig } from '../types'; @@ -118,7 +117,7 @@ export function Routes({ routes, fallback }: RoutesProps) { // React does not currently support Suspense when components are being server-side rendered // process.env.__IS_SERVER__: React.RenderToString() // window.__ICE_SSR_ENABLED__: React.hydrate() - const RenderComponent = isServer || (window as any).__ICE_SSR_ENABLED__ + const RenderComponent = process.env.__IS_SERVER__ || (window as any).__ICE_SSR_ENABLED__ ? (props: RouteComponentProps) => : (props: RouteComponentProps) => { return ( diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 00da7bc0b5..2445cc45c8 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,13 +1,5 @@ # Changelog -## 0.1.2 - -- [feat] export `isServer` - -## 0.1.1 - -- [feat] add `path-to-regexp@1.x` dependency - ## 0.1.0 - [feat] dependency of axios and utils for axios \ No newline at end of file diff --git a/packages/runtime/src/env.ts b/packages/runtime/src/env.ts deleted file mode 100644 index 030cc5f3ca..0000000000 --- a/packages/runtime/src/env.ts +++ /dev/null @@ -1,17 +0,0 @@ -interface GlobalEnv { - __IS_SERVER__: boolean; -} - -let globalPolyfill: unknown = typeof globalThis !== 'undefined' ? globalThis : null; - -if (!globalPolyfill) { - // add polyfill to globalThis - // eslint-disable-next-line no-nested-ternary - globalPolyfill = typeof window !== 'undefined' ? window : (typeof global !== 'undefined' ? global : {}); -} - -const isServer = (globalPolyfill as GlobalEnv).__IS_SERVER__; - -export { - isServer, -}; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index bc4b9f299c..6ffc7ebd4f 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,11 +1,9 @@ import axios from 'axios'; import * as pathToRegexp from 'path-to-regexp'; import axiosUtils from './axiosUtils'; -import { isServer } from './env'; export { axios, axiosUtils, - isServer, pathToRegexp, }; \ No newline at end of file From e68f178aaaed5d69955d31cedb36d28707a2a214 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Tue, 11 Jan 2022 14:24:42 +0800 Subject: [PATCH 06/20] feat: support cli option force to clear cache (#5117) --- packages/build-user-config/CHANGELOG.md | 4 +++ packages/build-user-config/package.json | 2 +- .../build-user-config/src/cliOption/force.js | 28 +++++++++++++++++++ .../src/config/option.config.js | 3 ++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 packages/build-user-config/src/cliOption/force.js diff --git a/packages/build-user-config/CHANGELOG.md b/packages/build-user-config/CHANGELOG.md index 50c3a87550..e1588784e3 100644 --- a/packages/build-user-config/CHANGELOG.md +++ b/packages/build-user-config/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.1.0 + +- [feat] support `--force` to empty cache folder + ## 2.0.4 - [fix] `minify: true` is invalid diff --git a/packages/build-user-config/package.json b/packages/build-user-config/package.json index f644a004d4..67485c988e 100644 --- a/packages/build-user-config/package.json +++ b/packages/build-user-config/package.json @@ -1,6 +1,6 @@ { "name": "@builder/user-config", - "version": "2.0.4", + "version": "2.1.0", "description": "Includes methods which are releated to set base user config for framework", "homepage": "", "license": "MIT", diff --git a/packages/build-user-config/src/cliOption/force.js b/packages/build-user-config/src/cliOption/force.js new file mode 100644 index 0000000000..ea7ed2faf6 --- /dev/null +++ b/packages/build-user-config/src/cliOption/force.js @@ -0,0 +1,28 @@ +const path = require('path'); +const fs = require('fs-extra'); + +function emptyDir(dir) { + if (fs.existsSync(dir)) { + fs.emptyDirSync(dir); + } +} + +module.exports = (...args) => { + const [/* config */, force, context, api] = args; + if (force) { + const { command, userConfig, rootDir } = context; + const { onHook } = api; + onHook(`before.${command}.run`, ({ config }) => { + if (userConfig.vite) { + // vite 模式缓存目录固定为 /node_modules/.vite + emptyDir(path.join(rootDir, 'node_modules', '.vite')); + } else { + config.forEach((webpackConfig) => { + if (webpackConfig.cache && webpackConfig.cache.cacheDirectory) { + emptyDir(webpackConfig.cache.cacheDirectory); + } + }); + } + }); + } +}; diff --git a/packages/build-user-config/src/config/option.config.js b/packages/build-user-config/src/config/option.config.js index 5ae01e54c1..c7e434ba3b 100644 --- a/packages/build-user-config/src/config/option.config.js +++ b/packages/build-user-config/src/config/option.config.js @@ -1,4 +1,7 @@ module.exports = { + force: { + commands: ['start', 'build'], + }, https: { commands: ['start'], }, From 75be5af984bb808a37b3f0ef8de36fe291c4477d Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Tue, 11 Jan 2022 14:57:28 +0800 Subject: [PATCH 07/20] fix: fail to open browser in mpa (#5122) --- packages/vite-service/CHANGELOG.md | 1 + packages/vite-service/package.json | 2 +- packages/vite-service/src/start.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vite-service/CHANGELOG.md b/packages/vite-service/CHANGELOG.md index efc5b66c8f..c0e3c0d2cb 100644 --- a/packages/vite-service/CHANGELOG.md +++ b/packages/vite-service/CHANGELOG.md @@ -5,6 +5,7 @@ - [feat] refactor html plugin for SSR render when development - [fix] lifecycle arguments for `after.build.compile` - [fix] pass vite config for lifecycle hooks +- [fix] fail to open browser in mpa ## 2.0.3 diff --git a/packages/vite-service/package.json b/packages/vite-service/package.json index 720e3fe5d6..3e4bc37574 100644 --- a/packages/vite-service/package.json +++ b/packages/vite-service/package.json @@ -1,6 +1,6 @@ { "name": "@builder/vite-service", - "version": "2.0.4", + "version": "2.1.0", "description": "vite implementation", "author": "ice-admin@alibaba-inc.com", "homepage": "", diff --git a/packages/vite-service/src/start.ts b/packages/vite-service/src/start.ts index 71f062fbe0..c1fe29b21d 100644 --- a/packages/vite-service/src/start.ts +++ b/packages/vite-service/src/start.ts @@ -50,7 +50,7 @@ export async function viteStart(context: Context): Promise { function generateDevServerUrl(devConfig) { const { server: { port, https, host } } = devConfig; const protocol = https ? 'https' : 'http'; - return `${protocol}://${resolveHostname(host)}:${port}`; + return `${protocol}://${resolveHostname(host)}:${port}/`; } // Reference: https://github.com/vitejs/vite/blob/7e3e84e1b733f4cb0cba3bd69f28a5671b52261c/packages/vite/src/node/utils.ts#L580 From 3807692e71bdbbd39dcd00cea5db47a8700cdbd8 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Tue, 11 Jan 2022 17:22:08 +0800 Subject: [PATCH 08/20] fix: process env for vite (#5123) --- packages/plugin-ice-ssr/package.json | 1 - packages/plugin-ice-ssr/src/index.ts | 12 +++++++ packages/plugin-ice-ssr/src/vite/ssrBuild.ts | 12 ++----- yarn.lock | 34 ++++---------------- 4 files changed, 22 insertions(+), 37 deletions(-) diff --git a/packages/plugin-ice-ssr/package.json b/packages/plugin-ice-ssr/package.json index 36de0f27cb..af408669ab 100644 --- a/packages/plugin-ice-ssr/package.json +++ b/packages/plugin-ice-ssr/package.json @@ -27,7 +27,6 @@ "@builder/webpack-config": "^2.0.0", "@loadable/babel-plugin": "^5.13.2", "@loadable/webpack-plugin": "^5.14.0", - "@rollup/plugin-replace": "^3.0.0", "chalk": "^4.0.0", "cheerio": "^1.0.0-rc.3", "deepmerge": "^4.2.2", diff --git a/packages/plugin-ice-ssr/src/index.ts b/packages/plugin-ice-ssr/src/index.ts index 0b2a042606..532ccb7d8c 100644 --- a/packages/plugin-ice-ssr/src/index.ts +++ b/packages/plugin-ice-ssr/src/index.ts @@ -40,6 +40,18 @@ const plugin: IPlugin = async (api): Promise => { } if (userConfig.vite) { + + // vite 模式下直接使用 process.env.__IS_SERVER__ 的变量,如果注册即便是将会进行字符串替换 + if (command === 'start') { + onGetWebpackConfig((config) => { + config + .plugin('DefinePlugin') + .tap(([args]) => { + delete args['process.env.__IS_SERVER__']; + return [args]; + }); + }); + } modifyUserConfig('vite.plugins', [vitePluginSSR(ssrEntry)], { deepmerge: true }); onHook('after.build.compile', async ({ config }) => { // dev 阶段直接使用 ssrLoadModule 实时加载并执行 diff --git a/packages/plugin-ice-ssr/src/vite/ssrBuild.ts b/packages/plugin-ice-ssr/src/vite/ssrBuild.ts index 9a2d91e1d6..672f0c2f8f 100644 --- a/packages/plugin-ice-ssr/src/vite/ssrBuild.ts +++ b/packages/plugin-ice-ssr/src/vite/ssrBuild.ts @@ -23,6 +23,9 @@ const ssrBuild = async (prodConfig: InlineConfig, buildOptions: Options): Promis [prodConfig, { // No need to copy public files to SSR directory publicDir: false, + define: { + 'process.env.__IS_SERVER__': true, + }, build: { minify: false, outDir: path.resolve(distDir, 'server'), @@ -36,15 +39,6 @@ const ssrBuild = async (prodConfig: InlineConfig, buildOptions: Options): Promis entryFileNames: '[name].js', chunkFileNames: '[name].js', }, - plugins: [ - // eslint-disable-next-line global-require - require('@rollup/plugin-replace')({ - preventAssignment: true, - values: { - 'process.env.__IS_SERVER__': true, - }, - }), - ], }, }, }], { arrayMerge } diff --git a/yarn.lock b/yarn.lock index 282a1f1d4b..37307dcedf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1116,6 +1116,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" + integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/standalone@^7.16.4": version "7.16.4" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.16.4.tgz#f62a5b14fc0e881668f26739f28bcdaacedd3080" @@ -3067,23 +3074,6 @@ dependencies: redux "^4.0.5" -"@rollup/plugin-replace@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz#3a4c9665d4e7a4ce2c360cf021232784892f3fac" - integrity sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg== - dependencies: - "@rollup/pluginutils" "^3.1.0" - magic-string "^0.25.7" - -"@rollup/pluginutils@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" - integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== - dependencies: - "@types/estree" "0.0.39" - estree-walker "^1.0.1" - picomatch "^2.2.2" - "@rollup/pluginutils@^4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" @@ -3224,11 +3214,6 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== -"@types/estree@0.0.39": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.26" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz#5d9a8eeecb9d5f9d7fc1d85f541512a84638ae88" @@ -7263,11 +7248,6 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" - integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== - estree-walker@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" From 659896ac220d5e52d0c8c33ddda52710967e34b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8B=92=E7=8B=92=E7=A5=9E?= Date: Wed, 12 Jan 2022 16:44:29 +0800 Subject: [PATCH 09/20] chore: revert helpers value (#5126) * chore: revert helpers value * chore: add changelog * chore: remove useless deps --- packages/build-app-helpers/CHANGELOG.md | 2 ++ packages/build-app-helpers/package.json | 3 +-- packages/build-app-helpers/src/injectTransformRuntime.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/build-app-helpers/CHANGELOG.md b/packages/build-app-helpers/CHANGELOG.md index a90941c832..74f72ebd25 100644 --- a/packages/build-app-helpers/CHANGELOG.md +++ b/packages/build-app-helpers/CHANGELOG.md @@ -2,9 +2,11 @@ ## 2.5.0 +- [chore] revert `@babel/plugin-transform-runtime` helpers value - [chore] bump version of `es-module-lexer` - [feat] support scan source code of import statement + ## 2.4.3 - [chore] not inline @babel/runtime/helpers code diff --git a/packages/build-app-helpers/package.json b/packages/build-app-helpers/package.json index 06391513bc..898f010cdc 100644 --- a/packages/build-app-helpers/package.json +++ b/packages/build-app-helpers/package.json @@ -28,7 +28,6 @@ "lodash": "^4.17.20", "esbuild": "^0.13.12", "es-module-lexer": "^0.9.0", - "fast-glob": "^3.2.7", - "@babel/runtime": "^7.16.7" + "fast-glob": "^3.2.7" } } diff --git a/packages/build-app-helpers/src/injectTransformRuntime.ts b/packages/build-app-helpers/src/injectTransformRuntime.ts index dba4897cb8..fc646a4a4b 100644 --- a/packages/build-app-helpers/src/injectTransformRuntime.ts +++ b/packages/build-app-helpers/src/injectTransformRuntime.ts @@ -11,7 +11,7 @@ export default function(config) { const targetPlugin = formatPath('@babel/plugin-transform-runtime'); const pluginOptions = { corejs: false, - helpers: true, + helpers: false, regenerator: true, useESModules: false, }; From 61b767a9447f6e7506910cf9796fe5a5c8121903 Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Wed, 12 Jan 2022 16:48:26 +0800 Subject: [PATCH 10/20] fix: plugin-store use the incorrect alias (#5113) * fix: get alias from userConfig object directly * fix: can't get correct alias * fix: layout nested store not work * fix: mpa store * chore: version * chore: remove default alias value --- packages/icejs/package.json | 2 +- packages/plugin-store/CHANGELOG.md | 4 + packages/plugin-store/package.json | 2 +- .../src/babelPluginReplacePath.ts | 16 ++-- packages/plugin-store/src/index.ts | 85 +++++++++++-------- .../plugin-store/src/replacePathLoader.ts | 3 - 6 files changed, 64 insertions(+), 48 deletions(-) diff --git a/packages/icejs/package.json b/packages/icejs/package.json index 28d79b1540..e29beeec65 100644 --- a/packages/icejs/package.json +++ b/packages/icejs/package.json @@ -33,7 +33,7 @@ "build-plugin-ice-request": "2.0.0", "build-plugin-ice-router": "2.1.1", "build-plugin-ice-ssr": "3.1.0", - "build-plugin-ice-store": "2.0.6", + "build-plugin-ice-store": "2.0.7", "build-plugin-react-app": "2.2.0", "build-plugin-pwa": "1.1.0", "build-plugin-speed": "1.0.0", diff --git a/packages/plugin-store/CHANGELOG.md b/packages/plugin-store/CHANGELOG.md index ddb25f05d9..d33de4b6ab 100644 --- a/packages/plugin-store/CHANGELOG.md +++ b/packages/plugin-store/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.7 + +- [fix] get alias from webpack config instead of userConfig object + ## 2.0.6 - [fix] update dependencies version diff --git a/packages/plugin-store/package.json b/packages/plugin-store/package.json index ab5277ba78..7b8cc41ed6 100644 --- a/packages/plugin-store/package.json +++ b/packages/plugin-store/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-ice-store", - "version": "2.0.6", + "version": "2.0.7", "description": "builtin `@ice/store` in icejs", "author": "ice-admin@alibaba-inc.com", "homepage": "https://github.com/alibaba/ice#readme", diff --git a/packages/plugin-store/src/babelPluginReplacePath.ts b/packages/plugin-store/src/babelPluginReplacePath.ts index b75690ef2d..14e06ee36a 100644 --- a/packages/plugin-store/src/babelPluginReplacePath.ts +++ b/packages/plugin-store/src/babelPluginReplacePath.ts @@ -88,10 +88,6 @@ interface IGetConfigRoutePathParams { // case3: { "@": "./src", "@/pages": "./src/pages" } function matchAliasPath(alias: IAlias, value: string, applyMethod: Function): string { let aliasPath = ''; - // use default alias - if (!Object.keys(alias).length) { - alias['@'] = 'src'; - } // use custom alias Object.keys(alias).forEach(currKey => { if (value.startsWith(currKey)) { @@ -129,12 +125,20 @@ function formatPagePath({ routesPath, value, alias, tempDir, applyMethod, rootDi if (/src\/pages\/\w+(.tsx|.jsx?)$/.test(value)) { return newValue; } else { - const [, , pageName] = matchedPagePath.split('/'); + // matchedPagePath 值示例: + // relativePath: ./pages/Home + // alias: /example/src/pages/Home + const pagePathParts = matchedPagePath.split('/'); + const pageName = pagePathParts[pagePathParts.length - 1]; newValue = pageName ? path.join(rootDir, tempDir, 'pages', pageName, 'index.tsx') : ''; } return newValue; } else if (matchedPagePath && layoutPathRegExp.test(matchedPagePath)) { - const [, , pageName] = matchedPagePath.split('/'); + // matchedPagePath 值示例: + // relativePath: ./pages/Home/Layout + // alias: /example/src/pages/Home/Layout + const pagePathParts = matchedPagePath.split('/'); + const pageName = pagePathParts[pagePathParts.length - 2]; const newValue = pageName ? path.join(rootDir, tempDir, 'pages', pageName, 'Layout') : ''; return newValue; } diff --git a/packages/plugin-store/src/index.ts b/packages/plugin-store/src/index.ts index ae36bf5ebc..3a05434ff8 100644 --- a/packages/plugin-store/src/index.ts +++ b/packages/plugin-store/src/index.ts @@ -8,12 +8,21 @@ import vitePluginPageRedirect from './vitePluginPageRedirect'; const { name: pluginName } = require('../package.json'); +interface IReplaceRouterPathOptions { + tempDir: string, + applyMethod: (...args: string[]) => void, + routesPaths: string, + rootDir:string, + srcPath: string, + alias: Record, +} + export default async (api: any) => { const { context, getValue, onHook, applyMethod, onGetWebpackConfig, modifyUserConfig } = api; - const { rootDir, userConfig } = context; + const { rootDir, userConfig, command } = context; // get mpa entries in src/pages - const { mpa: isMpa, entry, store, swc, alias, vite, router, babelPlugins = [] } = userConfig; + const { mpa: isMpa, entry, store, swc, vite, router, babelPlugins = [] } = userConfig; const tempPath = getValue('TEMP_PATH'); const srcDir = isMpa ? 'src' : applyMethod('getSourceDir', entry); @@ -79,53 +88,55 @@ export default async (api: any) => { routesPaths = [routes.routesPath]; } + // redirect route path to ice temp(.ice/pages/Home/index.tsx) if (vite) { modifyUserConfig( 'vite.plugins', [vitePluginPageRedirect(rootDir, routesPaths)], { deepmerge: true } ); - } + } else { + const defaultAlias = {}; + const options: IReplaceRouterPathOptions = { + tempDir, + applyMethod, + routesPaths, + rootDir, + srcPath, + alias: defaultAlias, + }; + onHook(`before.${command}.run`, ({ config }) => { + const webWebpackConfig = Array.isArray(config) ? (config.find(item => item.name === 'web') || {}) : config; + options.alias = webWebpackConfig?.resolve?.alias || defaultAlias; + }); - if (swc) { - onGetWebpackConfig((config: any) => { - config.module - .rule('replace-router-path') + if (swc) { + onGetWebpackConfig((config: any) => { + config.module + .rule('replace-router-path') // ensure that replace-router-path-loader is before babel-loader // @loadable/babel-plugin will transform the router paths which replace-router-path-loader couldn't transform - .after('tsx') - .test((filePath: string) => routesPaths.includes(filePath)) - .use('replace-router-path-loader') - .loader(require.resolve(path.join(__dirname, 'replacePathLoader'))) - .options({ - alias, - tempDir, - applyMethod, - routesPaths, - rootDir, - srcPath - }); - }); - } else { - const replacePathBabelPlugin = [ - require.resolve('./babelPluginReplacePath'), - { - routesPaths, - alias, - applyMethod, - tempDir, - rootDir - } - ]; - const loadableBabelPluginIndex = babelPlugins.indexOf('@loadable/babel-plugin'); - if (loadableBabelPluginIndex > -1) { + .after('tsx') + .test((filePath: string) => routesPaths.includes(filePath)) + .use('replace-router-path-loader') + .loader(require.resolve(path.join(__dirname, 'replacePathLoader'))) + .options(options); + }); + } else { + const replacePathBabelPlugin = [ + require.resolve('./babelPluginReplacePath'), + options + ]; + const loadableBabelPluginIndex = babelPlugins.indexOf('@loadable/babel-plugin'); + if (loadableBabelPluginIndex > -1) { // ensure ReplacePathBabelPlugin is before @loadable/babel-plugin // @loadable/babel-plugin will transform the router paths which babelPluginReplacePath couldn't transform - babelPlugins.splice(loadableBabelPluginIndex, 0, replacePathBabelPlugin); - } else { - babelPlugins.push(replacePathBabelPlugin); + babelPlugins.splice(loadableBabelPluginIndex, 0, replacePathBabelPlugin); + } else { + babelPlugins.push(replacePathBabelPlugin); + } + modifyUserConfig('babelPlugins', [...babelPlugins]); } - modifyUserConfig('babelPlugins', [...babelPlugins]); } onGetWebpackConfig((config: any) => { diff --git a/packages/plugin-store/src/replacePathLoader.ts b/packages/plugin-store/src/replacePathLoader.ts index 137c94ef86..9bc0403078 100644 --- a/packages/plugin-store/src/replacePathLoader.ts +++ b/packages/plugin-store/src/replacePathLoader.ts @@ -38,9 +38,6 @@ async function loader(content: string, sourceMap: SourceMap) { // ./pages/Home/index[.js|.jsx|.tsx] -> ./pages/Home originSourcePath = path.dirname(originSourcePath); } - if (!Object.keys(alias).length) { - alias['@'] = 'src'; - } let matchAliasKey = ''; Object.keys(alias).forEach((aliasKey: string) => { if (new RegExp(aliasKey).test(originSourcePath)) { From c16ad8f5088211ed2f4b9e9c7d471ded67672316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Wed, 12 Jan 2022 17:19:25 +0800 Subject: [PATCH 11/20] fix/be compatible for input like `src/main` (#5104) * fix: wrong matching logic in pluginIndexHtml (#5095) Co-authored-by: guibwl <228474838@qq.com> --- packages/vite-plugin-index-html/CHANGELOG.md | 4 ++ packages/vite-plugin-index-html/package.json | 2 +- packages/vite-plugin-index-html/src/index.ts | 28 ++++----- packages/vite-plugin-index-html/src/utils.ts | 14 ++++- .../tests/index.test.ts | 61 ++++++++++++++++++- .../vite-plugin-index-html/tests/utils.ts | 4 +- 6 files changed, 89 insertions(+), 24 deletions(-) diff --git a/packages/vite-plugin-index-html/CHANGELOG.md b/packages/vite-plugin-index-html/CHANGELOG.md index a2f6989e37..ea4ecb316b 100644 --- a/packages/vite-plugin-index-html/CHANGELOG.md +++ b/packages/vite-plugin-index-html/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.2 + +- [fix] be compatible for input like `src/main`. ([#5049](https://github.com/alibaba/ice/pull/5095)) + ## 2.0.1 - [fix] unexpected entry with too long root dir. diff --git a/packages/vite-plugin-index-html/package.json b/packages/vite-plugin-index-html/package.json index d0f1f114de..4e2e260c66 100644 --- a/packages/vite-plugin-index-html/package.json +++ b/packages/vite-plugin-index-html/package.json @@ -1,6 +1,6 @@ { "name": "vite-plugin-index-html", - "version": "2.0.1", + "version": "2.0.2", "description": "Vite Plugin that simplifies creation of HTML files to serve your bundles", "main": "lib/index.js", "keywords": [ diff --git a/packages/vite-plugin-index-html/src/index.ts b/packages/vite-plugin-index-html/src/index.ts index 0b9d97599d..446a0756a7 100644 --- a/packages/vite-plugin-index-html/src/index.ts +++ b/packages/vite-plugin-index-html/src/index.ts @@ -1,8 +1,8 @@ -import { extname, resolve, posix } from 'path'; +import { extname, resolve, normalize, join, isAbsolute } from 'path'; import { readFileSync, pathExists } from 'fs-extra'; import type { Plugin, ResolvedConfig, HtmlTagDescriptor } from 'vite'; import type { OutputBundle, OutputAsset, OutputChunk, PreserveEntrySignaturesOption } from 'rollup'; -import { isAbsoluteUrl, addTrailingSlash, formatPath, getEntryUrl, isSingleEntry } from './utils'; +import { isAbsoluteUrl, addTrailingSlash, formatPath, getEntryUrl, isSingleEntry, getRelativePath } from './utils'; import minifyHtml from './minifyHtml'; const scriptLooseRegex = /]*src=['"]?([^'"]*)['"]?[^>]*>*<\/script>/; @@ -134,6 +134,7 @@ export default function htmlPlugin(userOptions?: HtmlPluginOptions): Plugin { const html = injectToHtml( removeHtmlEntryScript( + viteConfig.root, parseTemplate(userOptions.template, userOptions.templateContent), getEntryUrl(userOptions.input), ), @@ -155,7 +156,7 @@ export default function htmlPlugin(userOptions?: HtmlPluginOptions): Plugin { tag: 'script', attrs: { type: 'module', - src: getRelativedPath( + src: getRelativePath( viteConfig.root, getEntryUrl(userOptions.input) ), @@ -164,6 +165,7 @@ export default function htmlPlugin(userOptions?: HtmlPluginOptions): Plugin { }; const _html = injectToHtml( removeHtmlEntryScript( + viteConfig.root, html, getEntryUrl(userOptions.input)), [entryTag], @@ -173,9 +175,12 @@ export default function htmlPlugin(userOptions?: HtmlPluginOptions): Plugin { }; } -export const removeHtmlEntryScript = (html: string, entry: string) => { +export const removeHtmlEntryScript = (rootDir: string, html: string, entry: string) => { let _html = html; - const _entry = formatPath(entry); + const _entry = formatPath( + isAbsolute(entry) ? entry : join(rootDir, entry) + ); + const matchs = html.match(new RegExp(scriptLooseRegex, 'g')); const commentScript = (script: string) => ``; @@ -183,8 +188,10 @@ export const removeHtmlEntryScript = (html: string, entry: string) => { if (matchs) { matchs.forEach((matchStr) => { const [, src] = matchStr.match(scriptLooseRegex); + // change `./src/index.js` to `src/index.js` + const normalizedSrc = normalize(src); - if (_entry.includes(src)) { + if (_entry.includes(normalizedSrc)) { _html = _html.replace(matchStr, commentScript(matchStr)); console.warn(` vite-plugin-index-html: For the reason that entry was configured, ${matchStr} is deleted. @@ -250,15 +257,6 @@ function toPublicPath(filename: string, config: ResolvedConfig) { return isAbsoluteUrl(filename) ? filename : addTrailingSlash(config.base) + filename; } -function getRelativedPath(rootDir: string, path: string): string { - const _rootDir = formatPath(rootDir); - let _path = formatPath(path); - if (path.includes(_rootDir)) { - _path = `/${posix.relative(_rootDir, _path)}`; - } - return _path; -} - const headInjectRE = /<\/head>/; const bodyInjectRE = /<\/body>/; diff --git a/packages/vite-plugin-index-html/src/utils.ts b/packages/vite-plugin-index-html/src/utils.ts index 66f399c07e..404f3fc942 100644 --- a/packages/vite-plugin-index-html/src/utils.ts +++ b/packages/vite-plugin-index-html/src/utils.ts @@ -1,4 +1,4 @@ -import * as path from 'path'; +import { posix, sep } from 'path'; /** * Check if link is absolute url @@ -22,7 +22,7 @@ export const addTrailingSlash = (str: string): string => { * @returns */ export const formatPath = (pathStr: string): string => { - return process.platform === 'win32' ? pathStr.split(path.sep).join('/') : pathStr; + return process.platform === 'win32' ? pathStr.split(sep).join('/') : pathStr; }; export const getEntryUrl = (entries: string | Record) => { @@ -48,3 +48,13 @@ export const isSingleEntry = (entries: string | Record) => { } return true; }; + +export function getRelativePath(rootDir: string, path: string): string { + const formatedRootDir = formatPath(rootDir); + let relativePath = formatPath(path); + + if (relativePath.includes(formatedRootDir)) { + relativePath = `/${posix.relative(formatedRootDir, relativePath)}`; + } + return relativePath; +} diff --git a/packages/vite-plugin-index-html/tests/index.test.ts b/packages/vite-plugin-index-html/tests/index.test.ts index cfef394d94..01a030bdf3 100644 --- a/packages/vite-plugin-index-html/tests/index.test.ts +++ b/packages/vite-plugin-index-html/tests/index.test.ts @@ -16,10 +16,10 @@ describe('vite-plugin-index-html', () => { ` - expect(removeHtmlEntryScript(html, '/icestark-demo/ice-vite/child/src/app')).toBe(html) + expect(removeHtmlEntryScript('/Users/ice', html, '/icestark-demo/ice-vite/child/src/app')).toBe(html) }) - test('remove-html-entry-script-vite', () => { + test('remove-html-entry-script-vite/src-irregular', () => { const html = ` @@ -34,8 +34,63 @@ describe('vite-plugin-index-html', () => { ` + const result = html.replace('', ''); - expect(removeHtmlEntryScript(html, '/icestark-demo/src/main.tsx')).toBe(html.replace('', '')) + expect(removeHtmlEntryScript('/Users/ice', html, '/icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, 'icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, './icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, '/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, './src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, 'src/main.tsx')).toBe(result) }) + test('remove-html-entry-script-vite/src-relative-A', () => { + const html = ` + + + + + + Vite App + + +
+ + + + ` + const result = html.replace('', ''); + + expect(removeHtmlEntryScript('/Users/ice', html, '/icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, 'icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, './icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, '/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, './src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, 'src/main.tsx')).toBe(result) + }) + + test('remove-html-entry-script-vite/src-relative-B', () => { + const html = ` + + + + + + Vite App + + +
+ + + + ` + const result = html.replace('', ''); + + expect(removeHtmlEntryScript('/Users/ice', html, '/icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, 'icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, './icestark-demo/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, '/src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, './src/main.tsx')).toBe(result) + expect(removeHtmlEntryScript('/Users/ice', html, 'src/main.tsx')).toBe(result) + }) }); diff --git a/packages/vite-plugin-index-html/tests/utils.ts b/packages/vite-plugin-index-html/tests/utils.ts index 6d859ee923..b1ca630577 100644 --- a/packages/vite-plugin-index-html/tests/utils.ts +++ b/packages/vite-plugin-index-html/tests/utils.ts @@ -1,18 +1,16 @@ -import { getTheFirstEntry, isSingleEntry } from '../src/utils'; +import { isSingleEntry } from '../src/utils'; describe('vite-plugin-index-html-utils', () => { test('entry', () => { let entry: string | Record = './src/main.tsx'; expect(isSingleEntry(entry)).toBeTruthy(); - expect(getTheFirstEntry(entry)).toBe('./src/main.tsx'); entry = { index: './src/main.tsx' } expect(isSingleEntry(entry)).toBeTruthy(); - expect(getTheFirstEntry(entry)).toBe('./src/main.tsx'); entry = { index: './src/main.tsx', From 46f70bb8bca825fdc95fe45e38f710424ed31871 Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Wed, 12 Jan 2022 20:04:01 +0800 Subject: [PATCH 12/20] feat: i18n (#5096) * feat: init plugin-auth * feat: init locale example * feat: generate locale routes * feat: reverse providers * feat: support get locales * feat: support get/set locale * chore: todo * chore: remove index.d.ts * feat: support ssr * feat: support ssg * feat: webpack-dev-server redirect * feat: support csr redirect * fix: activeLocale is not correct * feat: support localeRedirect and localeRoute * feat: modify history * feat: common function * feat: middleware * fix: render data * chore: add default locale config * chore: remove deps * chore: plugin version * fix: comment * test: add formatRoutes test * chore: deps * fix: could not call getInitialProps in some cases * chore: type * chore: type * chore: remove comment * fix: comment problem * feat: support basename * fix: can't redirect when enable basename * chore: remove log * chore: update peerDependencies version * fix: can't render in ssr * chore: locale to i18n * fix: comment * fix: display language error in csr --- examples/basic-i18n/build.json | 18 +++ examples/basic-i18n/package.json | 18 +++ examples/basic-i18n/public/favicon.png | Bin 0 -> 4943 bytes examples/basic-i18n/public/index.html | 15 +++ examples/basic-i18n/src/app.tsx | 34 +++++ .../src/components/LocaleSwitcher.tsx | 26 ++++ .../src/layouts/BasicLayout/index.module.less | 5 + .../src/layouts/BasicLayout/index.tsx | 13 ++ examples/basic-i18n/src/locales/index.ts | 2 + examples/basic-i18n/src/locales/locales.ts | 4 + examples/basic-i18n/src/locales/messages.ts | 22 ++++ examples/basic-i18n/src/pages/About/index.tsx | 21 ++++ examples/basic-i18n/src/pages/Home/index.tsx | 25 ++++ examples/basic-i18n/src/routes.ts | 25 ++++ examples/basic-i18n/src/typing.d.ts | 6 + examples/basic-i18n/tsconfig.json | 34 +++++ .../create-app-shared/src/runtimeModule.ts | 5 +- .../src/utils/getBuildConfig.ts | 23 ++++ packages/plugin-i18n/CHANGELOG.md | 5 + packages/plugin-i18n/README.md | 1 + packages/plugin-i18n/package.json | 36 ++++++ packages/plugin-i18n/src/_i18n.tsx | 14 +++ packages/plugin-i18n/src/constants.ts | 1 + packages/plugin-i18n/src/index.ts | 99 +++++++++++++++ packages/plugin-i18n/src/middleware.ts | 27 ++++ packages/plugin-i18n/src/runtime.tsx | 117 ++++++++++++++++++ .../plugin-i18n/src/templates/index.tsx.ejs | 88 +++++++++++++ packages/plugin-i18n/src/types.ts | 6 + .../utils/getDetectedLocaleFromPathname.ts | 34 +++++ .../plugin-i18n/src/utils/getLocaleData.ts | 100 +++++++++++++++ .../src/utils/normalizeLocalePath.ts | 30 +++++ .../plugin-i18n/src/utils/replaceBasename.ts | 21 ++++ packages/plugin-i18n/tsconfig.json | 12 ++ .../plugin-ice-ssr/src/renderPages.ts.ejs | 59 +++++++-- packages/plugin-ice-ssr/src/server.ts.ejs | 50 ++++++-- packages/plugin-router/src/runtime/Router.tsx | 1 - .../plugin-router/tests/formatRoutes.spec.ts | 28 ++++- packages/react-app-renderer/src/server.tsx | 20 +-- tsconfig.json | 3 +- yarn.lock | 28 +++++ 40 files changed, 1043 insertions(+), 33 deletions(-) create mode 100644 examples/basic-i18n/build.json create mode 100644 examples/basic-i18n/package.json create mode 100644 examples/basic-i18n/public/favicon.png create mode 100644 examples/basic-i18n/public/index.html create mode 100644 examples/basic-i18n/src/app.tsx create mode 100644 examples/basic-i18n/src/components/LocaleSwitcher.tsx create mode 100644 examples/basic-i18n/src/layouts/BasicLayout/index.module.less create mode 100644 examples/basic-i18n/src/layouts/BasicLayout/index.tsx create mode 100644 examples/basic-i18n/src/locales/index.ts create mode 100644 examples/basic-i18n/src/locales/locales.ts create mode 100644 examples/basic-i18n/src/locales/messages.ts create mode 100644 examples/basic-i18n/src/pages/About/index.tsx create mode 100644 examples/basic-i18n/src/pages/Home/index.tsx create mode 100644 examples/basic-i18n/src/routes.ts create mode 100644 examples/basic-i18n/src/typing.d.ts create mode 100644 examples/basic-i18n/tsconfig.json create mode 100644 packages/plugin-i18n/CHANGELOG.md create mode 100644 packages/plugin-i18n/README.md create mode 100644 packages/plugin-i18n/package.json create mode 100644 packages/plugin-i18n/src/_i18n.tsx create mode 100644 packages/plugin-i18n/src/constants.ts create mode 100644 packages/plugin-i18n/src/index.ts create mode 100644 packages/plugin-i18n/src/middleware.ts create mode 100644 packages/plugin-i18n/src/runtime.tsx create mode 100644 packages/plugin-i18n/src/templates/index.tsx.ejs create mode 100644 packages/plugin-i18n/src/types.ts create mode 100644 packages/plugin-i18n/src/utils/getDetectedLocaleFromPathname.ts create mode 100644 packages/plugin-i18n/src/utils/getLocaleData.ts create mode 100644 packages/plugin-i18n/src/utils/normalizeLocalePath.ts create mode 100644 packages/plugin-i18n/src/utils/replaceBasename.ts create mode 100644 packages/plugin-i18n/tsconfig.json diff --git a/examples/basic-i18n/build.json b/examples/basic-i18n/build.json new file mode 100644 index 0000000000..d7fa1a93fa --- /dev/null +++ b/examples/basic-i18n/build.json @@ -0,0 +1,18 @@ +{ + "plugins": [ + [ + "build-plugin-ice-i18n", + { + "locales": [ + "zh-CN", + "en-US" + ], + "defaultLocale": "zh-CN", + "redirect": false, + "i18nRouting": true + } + ] + ], + "vite": false, + "ssr": "static" +} \ No newline at end of file diff --git a/examples/basic-i18n/package.json b/examples/basic-i18n/package.json new file mode 100644 index 0000000000..b78ab285db --- /dev/null +++ b/examples/basic-i18n/package.json @@ -0,0 +1,18 @@ +{ + "dependencies": { + "react": "^17.0.0", + "react-dom": "^17.0.0", + "react-intl": "^5.22.0" + }, + "devDependencies": { + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0" + }, + "scripts": { + "start": "RUNTIME_DEBUG=true ../../packages/icejs/bin/ice-cli.js start", + "build": "../../packages/icejs/bin/ice-cli.js build" + }, + "engines": { + "node": ">=8.0.0" + } +} diff --git a/examples/basic-i18n/public/favicon.png b/examples/basic-i18n/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..cff1ee57cfc4c3968735eb2bb2be07aff37c44f4 GIT binary patch literal 4943 zcmbVQ`A^&D9X4PNlYmWtn2>Nd0TTnp1{{vWfQt>m+*iYq03i@=3?Y;=*xIG2+9}#j ztCVbO*JawY?T2bvm#JNUXwtRSlBG)*rCOtCtEy?6wEPQu9zJirc1eHORz08dJ-+Yz zIbNJTe*3M?_LE78nTa}`F3I2I>xO*%_FKXe;zYtRY1iozC;Yx=de>k9vvWP?GayIO z8^jUiP1_iS`oVAZf+v8SaWUX!9*K6Xp_#Qc$uCnLTzi4(B26uvZe^;}Pqmj^=I9P4>Xii7BI^yQq#5}x*!%KL$ z;;6F8#-gMmnXk{DX|bB2r>c0Zr$rC~2^ z$z~R5b@oTEL9~R=#Li>sod5y3peoftH47`=$FF=1p6(eL6~1;B(#zKjU^)b>&G)}* z&D0-)GM(|+&tKrB4CP&en*o^-$>XD*TMVzoNCD`QHsd{pm=`|=;-LgN5PYH+G*1;P z^dzslkcq}vq*%>-g`Vv_3SJMokU2QWgA9UJKmZ>Mf&C<;O8K&{#0E?4CgVO| zYHT!AZaV;)WFh3Vg8`>N8BC=iI*NP5oHBx^#I(Kteahs}T1?KM-wxfRE}IiPmdgMX zg8)l%Xz@IxBQ5h_xH@G_&49?_6anwSdjUZnDb_#H-BzIYm5EQ|wW9GjtIJ?a+X~xy z`T$r~Dvwhbztj5mvRgNbSeL*a&??AVYWw6RwWm4yFg(1m1GEK-L=%jS-@m3FJO1I; zfQ5{hYH)iqbRGrRCj#7;2*z^(7e#<4F&5m?`0!WY!S}8@)TOO3|MF$m7R12d-S0vG zO)KgzY6b6WNE+t*0`!V-yL(QVyR2DamEYRl=@_vhdAsf2^+-E=3fAb(?X4r)z~aZ; zT0`oZsI~@?t<{i5OYV}oRE@ZKZlFs9OW6P5hb+#+Ax6+-;xaxr^}P1j=_%VnGed6T zYsXt1G2OkA$^@)~fEKS0yo$*J>O+g7nBBB)J`c`SPuh7vb!KfRoRv+@GZ2D=YC}G> zMQ$z7>s@RQ9_AvOqXy)kW-PfQ5bXbuqgP4s4<9nl_+-=}PQG8)P zxGMSsP*rV0bEl}M#T1=c#)|4=ke$cD1gx3jO0P$Aa2pm@S6TuSw8k4nA{|B_Xu=%n z{p|WTixOktNfNJWw{<_v)YMidgr40vCc~D8KZer4eE=Rgj}K@hCQ=e?Lly8?AHEpi zAp-2%Wtn2-z8Z?T^UZBm9x%#K=d117GZL&Ruw6@^`)Q+6D+|Df4K_{Jly%-`7dpln z&uKZAhlSTL6Y}$av z(?>$(gx_I=`vYS$)gkj)1G@7DSW`(4O9D9aBRm$SJyA7ENVtAj% z!o4@e$r8ywBKo~+ke`0(1*Tw%F?E3&Q!X-Ad|DX=0V6rY^Z^C4IZK_Q8iqvn^bL!` z=A7U9@P=5Ce8Om%y%?J1SAgTIRrW4XDGVUqIlkw#$`gwT$k~7Q2dad{q4A)q265O$ zuy09wxT$)9*d=*pR&f3>-(?`nItZTaInCN$;_sR-gysI%BmL~((7UgL^NcL=)jH;~{2Um-P;e3uF9MvC`^B13DIbNY)MBVi@(|xd14DZIYPrKW9lg@QCqcs{ zTD6*KEaRJ`A#;uA3s}KS3t*vSR$cEOSn*#7cAXit3Mw)@{MitsI^>edS9%sd7ffxj zcNm}mcdmd{j!%Mbm6r8z{yd2LB-_E3v%xlReUDd3%$RczEU!QLcQxk$&29_7$O-Bd zW4Khvx}iEig3>5w88jAr6R7pizXVq`m<9C=Xcr1Jdk-rM=x%o%;4Y6|6q~QYqn|QX zf&j$M4=25+5-O*b%E%rUFEh_};C+EcP$g?>$rX + + + + + + + icejs · i18n example + + + +
+ + + diff --git a/examples/basic-i18n/src/app.tsx b/examples/basic-i18n/src/app.tsx new file mode 100644 index 0000000000..6e5899b257 --- /dev/null +++ b/examples/basic-i18n/src/app.tsx @@ -0,0 +1,34 @@ +import { runApp, IAppConfig, useLocale, getDefaultLocale } from 'ice'; +import { IntlProvider as ReactIntlProvider } from 'react-intl'; +import { messages } from './locales'; + +function IntlProvider({ children }) { + const [locale] = useLocale(); + const defaultLocale = getDefaultLocale(); + + return ( + + {children} + + ); +} + +const appConfig: IAppConfig = { + router: { + type: 'browser', + basename: '/ice' + }, + app: { + addProvider: ({ children }) => { + return ( + {children} + ); + } + } +}; + +runApp(appConfig); \ No newline at end of file diff --git a/examples/basic-i18n/src/components/LocaleSwitcher.tsx b/examples/basic-i18n/src/components/LocaleSwitcher.tsx new file mode 100644 index 0000000000..cb48889f42 --- /dev/null +++ b/examples/basic-i18n/src/components/LocaleSwitcher.tsx @@ -0,0 +1,26 @@ +import { FormattedMessage } from 'react-intl'; +import { useLocale, getAllLocales, Link, useLocation } from 'ice'; + +export default function LocaleSwitcher() { + const location = useLocation(); + + const [activeLocale, setLocale] = useLocale(); + const allLocales = getAllLocales(); + const otherLocales = allLocales.filter((locale) => activeLocale !== locale); + return ( +
+

:

+
    + { + otherLocales.map((locale: string) => { + return ( +
  • + setLocale(locale)}>{locale} +
  • + ); + }) + } +
+
+ ); +} diff --git a/examples/basic-i18n/src/layouts/BasicLayout/index.module.less b/examples/basic-i18n/src/layouts/BasicLayout/index.module.less new file mode 100644 index 0000000000..4f0064222a --- /dev/null +++ b/examples/basic-i18n/src/layouts/BasicLayout/index.module.less @@ -0,0 +1,5 @@ +.basicLayout { + h1 { + color: red; + } +} \ No newline at end of file diff --git a/examples/basic-i18n/src/layouts/BasicLayout/index.tsx b/examples/basic-i18n/src/layouts/BasicLayout/index.tsx new file mode 100644 index 0000000000..cfbb735f00 --- /dev/null +++ b/examples/basic-i18n/src/layouts/BasicLayout/index.tsx @@ -0,0 +1,13 @@ +import { FormattedMessage } from 'react-intl'; +import styles from './index.module.less'; + +function BasicLayout({ children }) { + return ( +
+

+ {children} +
+ ); +} + +export default BasicLayout; diff --git a/examples/basic-i18n/src/locales/index.ts b/examples/basic-i18n/src/locales/index.ts new file mode 100644 index 0000000000..e367efff9f --- /dev/null +++ b/examples/basic-i18n/src/locales/index.ts @@ -0,0 +1,2 @@ +export * from './locales'; +export * from './messages'; \ No newline at end of file diff --git a/examples/basic-i18n/src/locales/locales.ts b/examples/basic-i18n/src/locales/locales.ts new file mode 100644 index 0000000000..f37ddfcdea --- /dev/null +++ b/examples/basic-i18n/src/locales/locales.ts @@ -0,0 +1,4 @@ +export const LOCALES = { + ENGLISH: 'en-US', + ZH_CN: 'zh-CN' +}; diff --git a/examples/basic-i18n/src/locales/messages.ts b/examples/basic-i18n/src/locales/messages.ts new file mode 100644 index 0000000000..263140ccd8 --- /dev/null +++ b/examples/basic-i18n/src/locales/messages.ts @@ -0,0 +1,22 @@ +import { LOCALES } from './locales'; + +export const messages = { + [LOCALES.ENGLISH]: { + homeTitle: 'Home', + aboutTitle: 'About', + currentLocale: 'Current locale', + defaultLocale: 'Default locale', + configuredLocales: 'Configured locales', + localeSwitcher: 'Locale Switcher', + basicLayout: 'BasicLayout', + }, + [LOCALES.ZH_CN]: { + homeTitle: '首页', + aboutTitle: '关于', + currentLocale: '当前语言', + defaultLocale: '默认语言', + configuredLocales: '配置的语言', + localeSwitcher: '语言切换', + basicLayout: '主布局' + }, +}; \ No newline at end of file diff --git a/examples/basic-i18n/src/pages/About/index.tsx b/examples/basic-i18n/src/pages/About/index.tsx new file mode 100644 index 0000000000..5d9ed85777 --- /dev/null +++ b/examples/basic-i18n/src/pages/About/index.tsx @@ -0,0 +1,21 @@ +import { useLocale, getAllLocales, getDefaultLocale, Link } from 'ice'; +import { FormattedMessage } from 'react-intl'; +import LocaleSwitcher from '@/components/LocaleSwitcher'; + +function About() { + const [locale] = useLocale(); + const allLocales = getAllLocales(); + const defaultLocale = getDefaultLocale(); + return ( +
+

+
: {JSON.stringify(allLocales)}
+
: {defaultLocale}
+
: {locale}
+ Home + +
+ ); +} + +export default About; diff --git a/examples/basic-i18n/src/pages/Home/index.tsx b/examples/basic-i18n/src/pages/Home/index.tsx new file mode 100644 index 0000000000..e70da9ff76 --- /dev/null +++ b/examples/basic-i18n/src/pages/Home/index.tsx @@ -0,0 +1,25 @@ +import { useLocale, getAllLocales, getDefaultLocale, Link, getLocale } from 'ice'; +import { FormattedMessage } from 'react-intl'; +import LocaleSwitcher from '@/components/LocaleSwitcher'; + +function Home() { + const [locale] = useLocale(); + const allLocales = getAllLocales(); + const defaultLocale = getDefaultLocale(); + return ( +
+

+
: {JSON.stringify(allLocales)}
+
: {defaultLocale}
+
: {locale}
+ About + +
+ ); +} + +Home.getInitialProps = function () { + console.log('getLocale in getInitialProps', getLocale()); +}; + +export default Home; diff --git a/examples/basic-i18n/src/routes.ts b/examples/basic-i18n/src/routes.ts new file mode 100644 index 0000000000..5dc4067858 --- /dev/null +++ b/examples/basic-i18n/src/routes.ts @@ -0,0 +1,25 @@ +import { IRouterConfig } from 'ice'; +import BasicLayout from '@/layouts/BasicLayout'; +import Home from '@/pages/Home'; +import About from '@/pages/About'; + +const routerConfig: IRouterConfig[] = [ + { + path: '/', + component: BasicLayout, + children: [ + { + path: '/about', + exact: true, + component: About + }, + { + path: '/', + exact: true, + component: Home, + }, + ] + }, +]; + +export default routerConfig; diff --git a/examples/basic-i18n/src/typing.d.ts b/examples/basic-i18n/src/typing.d.ts new file mode 100644 index 0000000000..5fabf57ff4 --- /dev/null +++ b/examples/basic-i18n/src/typing.d.ts @@ -0,0 +1,6 @@ +declare module '*.module.less' { + const classes: { [key: string]: string }; + export default classes; +} + +declare module '*.svg' \ No newline at end of file diff --git a/examples/basic-i18n/tsconfig.json b/examples/basic-i18n/tsconfig.json new file mode 100644 index 0000000000..001d4c91da --- /dev/null +++ b/examples/basic-i18n/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "react-jsx", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "rootDir": "./", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true, + "types": ["node"], + "paths": { + "@/*": ["./src/*"], + "ice": [".ice/index.ts"], + "ice/*": [".ice/pages/*"] + } + }, + "include": ["src/*"], + "exclude": ["node_modules", "build", "public"] +} diff --git a/packages/create-app-shared/src/runtimeModule.ts b/packages/create-app-shared/src/runtimeModule.ts index 91d946c093..df583dbcfa 100644 --- a/packages/create-app-shared/src/runtimeModule.ts +++ b/packages/create-app-shared/src/runtimeModule.ts @@ -136,7 +136,7 @@ class RuntimeModule { return this.AppProvider.reduce((ProviderComponent, CurrentProvider) => { return ({ children, ...rest }) => { const element = CurrentProvider - ? this.context.createElement(CurrentProvider, {...rest}, children) + ? this.context.createElement(CurrentProvider, { ...rest }, children) : children; return this.context.createElement( ProviderComponent, @@ -187,7 +187,8 @@ class RuntimeModule { } private addProvider: AddProvider = (Provider) => { - this.AppProvider.push(Provider); + // must promise user's providers are wrapped by the plugins' providers + this.AppProvider.unshift(Provider); } private addDOMRender: AddDOMRender = (render) => { diff --git a/packages/plugin-app-core/src/utils/getBuildConfig.ts b/packages/plugin-app-core/src/utils/getBuildConfig.ts index eae4fef82e..c6fbe03422 100644 --- a/packages/plugin-app-core/src/utils/getBuildConfig.ts +++ b/packages/plugin-app-core/src/utils/getBuildConfig.ts @@ -1,7 +1,15 @@ +interface I18nConfig { + locales: string[]; + defaultLocale: string; + redirect?: true; + i18nRouting?: false; +} + interface IBuildConfig { router?: object | boolean; store?: boolean; icestarkType?: 'es' | 'umd' | 'normal'; + i18n?: I18nConfig; web?: object; mpa?: boolean; } @@ -30,6 +38,21 @@ function getBuildConfig(userConfig): IBuildConfig{ // eslint-disable-next-line no-nested-ternary buildConfig.icestarkType = vite ? 'es' : (isIcestarkUMD ? 'umd' : 'normal'); + let i18nConfig; + if (plugins && Array.isArray(plugins)) { + plugins.forEach((plugin) => { + if (Array.isArray(plugin)) { + const [pluginName, pluginOptions] = plugin; + if (pluginName === 'build-plugin-ice-i18n') { + i18nConfig = pluginOptions; + } + } + }); + } + if (i18nConfig) { + buildConfig.i18n = i18nConfig; + } + return buildConfig; } diff --git a/packages/plugin-i18n/CHANGELOG.md b/packages/plugin-i18n/CHANGELOG.md new file mode 100644 index 0000000000..7542f661f5 --- /dev/null +++ b/packages/plugin-i18n/CHANGELOG.md @@ -0,0 +1,5 @@ +# build-plugin-ice-i18n + +## 0.1.0 + +- feat: init plugin diff --git a/packages/plugin-i18n/README.md b/packages/plugin-i18n/README.md new file mode 100644 index 0000000000..91b74dbf93 --- /dev/null +++ b/packages/plugin-i18n/README.md @@ -0,0 +1 @@ +# build-plugin-ice-i18n diff --git a/packages/plugin-i18n/package.json b/packages/plugin-i18n/package.json new file mode 100644 index 0000000000..492e50a855 --- /dev/null +++ b/packages/plugin-i18n/package.json @@ -0,0 +1,36 @@ +{ + "name": "build-plugin-ice-i18n", + "version": "0.1.0", + "description": "ICE i18n build-scripts plugin", + "author": "ice-admin@alibaba-inc.com", + "homepage": "https://github.com/alibaba/ice#readme", + "license": "MIT", + "files": [ + "lib", + "template", + "src", + "!lib/**/*.map" + ], + "main": "lib/index.js", + "dependencies": { + "universal-cookie": "^4.0.4", + "accept-language-parser": "^1.5.0", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + }, + "devDependencies": { + "build-scripts": "^1.2.1", + "@types/accept-language-parser": "^1.5.3", + "@types/history": "^4.7.5" + }, + "repository": { + "type": "http", + "url": "https://github.com/alibaba/ice/tree/master/packages/plugin-i18n" + }, + "bugs": { + "url": "https://github.com/alibaba/ice/issues" + } +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/_i18n.tsx b/packages/plugin-i18n/src/_i18n.tsx new file mode 100644 index 0000000000..2c3d166ac7 --- /dev/null +++ b/packages/plugin-i18n/src/_i18n.tsx @@ -0,0 +1,14 @@ +// 该文件仅用于 module.tsx 中引用 $ice/i18n 的编译问题 +import * as React from 'react'; + +export const I18nProvider = ({ children }) => { + return (
{children}
); +}; + +export function getLocale() { + return ''; +} + +export function getLocaleFromCookies() { + return ''; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/constants.ts b/packages/plugin-i18n/src/constants.ts new file mode 100644 index 0000000000..900311ba96 --- /dev/null +++ b/packages/plugin-i18n/src/constants.ts @@ -0,0 +1 @@ +export const LOCALE_COOKIE_KEY = 'ice_locale'; diff --git a/packages/plugin-i18n/src/index.ts b/packages/plugin-i18n/src/index.ts new file mode 100644 index 0000000000..b83223a239 --- /dev/null +++ b/packages/plugin-i18n/src/index.ts @@ -0,0 +1,99 @@ +import { IPluginAPI } from 'build-scripts'; +import path from 'path'; +import { I18nConfig } from './types'; +import { LOCALE_COOKIE_KEY } from './constants'; +import { expressI18nMiddleware } from './middleware'; + +export default async function ( + { onGetWebpackConfig, getValue, applyMethod, context }: IPluginAPI, + i18nConfig: I18nConfig, +) { + checkI18nConfig(i18nConfig); + + const { i18nRouting = true, redirect = false } = i18nConfig; + + const { userConfig: { ssr } } = context; + + const iceTemp = getValue('TEMP_PATH'); + const i18nTempDir = path.join(iceTemp, 'plugins', 'i18n'); + + applyMethod('modifyRenderData', (originRenderData) => { + return { ...originRenderData, i18n: { ...i18nConfig } }; + }); + + // copy templates to .ice/i18n dir + applyMethod( + 'addPluginTemplate', + path.join(__dirname, 'templates'), + { LOCALE_COOKIE_KEY, i18nConfig, i18nRouting } + ); + applyMethod( + 'addPluginTemplate', + { + template: path.join(__dirname, '../src/utils'), + targetDir: 'i18n/utils', + }, + ); + + onGetWebpackConfig((config) => { + config.resolve.alias.set('$ice/i18n', path.join(i18nTempDir, 'index.tsx')); + + const onBeforeSetupMiddleware = config.devServer.get('onBeforeSetupMiddleware'); // webpack 5 + const originalDevServeBefore = onBeforeSetupMiddleware || config.devServer.get('before'); + + function devServerBefore(...args: any[]) { + const [devServer] = args; + let app; + if (onBeforeSetupMiddleware) { + app = devServer.app; + } else { + app = devServer; + } + + const pattern = /^\/?((?!\.(js|css|map|json|png|jpg|jpeg|gif|svg|eot|woff2|ttf|ico)).)*$/; + app.get(pattern, expressI18nMiddleware(i18nConfig)); + + if (typeof originalDevServeBefore === 'function') { + originalDevServeBefore(...args); + } + } + + if (ssr && redirect) { + if (onBeforeSetupMiddleware) { + config.merge({ + devServer: { + onBeforeSetupMiddleware: devServerBefore, + } + }); + } else { + config.merge({ + devServer: { + before: devServerBefore, + } + }); + } + } + }); + + // export API + // import { useLocale, getAllLocales, getDefaultLocale, getLocale } from 'ice'; + applyMethod( + 'addExport', + { + source: './plugins/i18n', + importSource: '$$ice/plugins/i18n', + exportMembers: ['useLocale', 'getAllLocales', 'getDefaultLocale', 'getLocale'] + }); +} + +function checkI18nConfig(i18nConfig: I18nConfig) { + const { locales, defaultLocale } = i18nConfig; + if (!locales) { + console.error(`[build-plugin-ice-i18n] locales should be array but received ${typeof locales}`); + process.exit(1); + } + if (!defaultLocale) { + console.error(`[build-plugin-ice-i18n] defaultLocale should be string but received ${typeof defaultLocale}`); + process.exit(1); + } +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/middleware.ts b/packages/plugin-i18n/src/middleware.ts new file mode 100644 index 0000000000..b569969707 --- /dev/null +++ b/packages/plugin-i18n/src/middleware.ts @@ -0,0 +1,27 @@ +import { I18nConfig } from './types'; +import getLocaleData from './utils/getLocaleData'; + +export function expressI18nMiddleware(i18nConfig: I18nConfig, basename?: string) { + return function (req, res, next) { + const { headers, _parsedUrl } = req; + const { redirectUrl } = getLocaleData({ headers, url: _parsedUrl, i18nConfig, basename }); + if (redirectUrl) { + res.redirect(307, redirectUrl); + } else { + next(); + } + }; +} + +export function koaI18nMiddleware(i18nConfig: I18nConfig, basename?: string) { + return async function (ctx, next) { + const { req, res } = ctx; + const { headers, _parsedUrl } = req; + const { redirectUrl } = getLocaleData({ headers, url: _parsedUrl, i18nConfig, basename }); + + if (redirectUrl) { + res.redirect(307, redirectUrl); + } + await next(); + }; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/runtime.tsx b/packages/plugin-i18n/src/runtime.tsx new file mode 100644 index 0000000000..d38f7b4d94 --- /dev/null +++ b/packages/plugin-i18n/src/runtime.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { History } from 'history'; +import Cookies from 'universal-cookie'; +import { I18nProvider, getLocaleFromCookies } from '$ice/i18n'; +import { LOCALE_COOKIE_KEY } from './constants'; +import getLocaleData from './utils/getLocaleData'; +import { I18nConfig } from './types'; +import normalizeLocalePath from './utils/normalizeLocalePath'; + +export default ({ modifyRoutes, buildConfig, addProvider, appConfig }) => { + const { i18n: i18nConfig } = buildConfig; + const { defaultLocale, locales, i18nRouting } = i18nConfig; + const { router: appConfigRouter = {} } = appConfig; + const { history = {}, basename } = appConfigRouter; + const originHistory = { ...history }; + + if (i18nRouting !== false) { + modifyRoutes((routes) => { + // 该 routes 值是被 formatRoutes 方法处理后返回的结果 + return addRoutesByLocales(routes, locales, defaultLocale); + }); + } + + addProvider(Provider()); + + if (!process.env.__IS_SERVER__) { + const { redirectUrl, detectedLocale } = getLocaleData({ url: window.location, i18nConfig, basename }); + + setInitICELocaleToCookie(detectedLocale); + + if (redirectUrl) { + console.log(`[icejs locale plugin]: redirect to ${redirectUrl}`); + originHistory.push(redirectUrl); + } + } + + if (i18nRouting !== false) { + modifyHistory(history, i18nConfig, basename); + } +}; + +function addRoutesByLocales(originRoutes: any[], locales: string[], defaultLocale: string) { + // the locales which are need to add the prefix to the route(e.g.: /home -> /en-US/home). + const prefixRouteLocales = locales.filter(locale => locale !== defaultLocale); + + if (!prefixRouteLocales.length) { + return originRoutes; + } + const modifiedRoutes = [...originRoutes]; + + prefixRouteLocales.forEach((prefixRouteLocale: string) => { + originRoutes.forEach((originRoute) => { + const { children, path } = originRoute; + if (!children) { + modifiedRoutes.unshift({ + ...originRoute, + path: `/${prefixRouteLocale}${path[0] === '/' ? path :`/${path}`}`, + }); + } else { + modifiedRoutes.unshift({ + ...originRoute, + path: `/${prefixRouteLocale}${path[0] === '/' ? path :`/${path}`}`, + children: addRoutesByLocales(children, locales, defaultLocale), + }); + } + }); + }); + + return modifiedRoutes; +} + +function Provider() { + return function({ children }) { + return ( + + {children} + + ); + }; +} + +function setInitICELocaleToCookie(locale: string) { + const cookies = new Cookies(); + const iceLocale = cookies.get(LOCALE_COOKIE_KEY); + if (!iceLocale) { + cookies.set(LOCALE_COOKIE_KEY, locale); + } +} + +function modifyHistory(history: History, i18nConfig: I18nConfig, basename?: string) { + const originHistory = { ...history }; + const { defaultLocale } = i18nConfig; + + function getLocalePath( + pathname: string, + locale: string, + ) { + const localePathResult = normalizeLocalePath(pathname, i18nConfig, basename); + + if (locale === defaultLocale) { + return localePathResult.pathname; + } + return `/${locale}${localePathResult.pathname === '/' ? '' : localePathResult.pathname}`; + } + + history.push = function(path: string, state?: unknown) { + const locale = getLocaleFromCookies() || defaultLocale; + const localePath = getLocalePath(path, locale); + originHistory.push(localePath, state); + }; + + history.replace = function(path: string, state?: unknown) { + const locale = getLocaleFromCookies() || defaultLocale; + const localePath = getLocalePath(path, locale); + originHistory.replace(localePath, state); + }; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/templates/index.tsx.ejs b/packages/plugin-i18n/src/templates/index.tsx.ejs new file mode 100644 index 0000000000..b1307d21d2 --- /dev/null +++ b/packages/plugin-i18n/src/templates/index.tsx.ejs @@ -0,0 +1,88 @@ +<% if(!hasJsxRuntime) { %> + import * as React from 'react'; +<% } %> +import { createContext, useContext, useState } from 'react'; +import Cookies from 'universal-cookie'; +<% if (i18nRouting !== false) {%> +import { history } from 'create-app-shared'; +import getDetectedLocaleFromPathname from './utils/getDetectedLocaleFromPathname'; +import { getAppConfig } from '../../core/appConfig'; +<% } %> + +const LOCALE_COOKIE_KEY = '<%= LOCALE_COOKIE_KEY %>'; +const i18nConfig = <%- JSON.stringify(i18nConfig) %>; + +const { defaultLocale, locales } = i18nConfig; + +const Context = createContext(null); + +export const I18nProvider = ({ children }) => { + const [activeLocale, setActiveLocale] = useState(() => getLocale()); + + function setLocale(locale: string) { + setLocaleToCookies(locale); + setActiveLocale(locale); + } + + return ( + + {children} + + ) +} + +/** + * 在组件中获取和修改语言的 Hook + */ +export function useLocale(): [string, React.Dispatch>] { + return useContext(Context) +} + +/** + * 获取配置的默认语言 + */ +export function getDefaultLocale(): string { + return defaultLocale +} + +/** + * 获取所有配置的国际化语言 + */ +export function getAllLocales(): string[] { + return locales; +} + +<% if (i18nRouting !== false) {%> +function getDetectedLocaleFromPath(locales: string[], defaultLocale: string) { + const appConfig = getAppConfig(); + if (history === null) { + console.log('history is null, can\'t get current locale from pathname'); + return; + } + const { location: { pathname } } = history; + + return getDetectedLocaleFromPathname({ pathname, locales, defaultLocale, basename: appConfig?.router?.basename }); +} +<% } %> + +/** + * 获取当前页面的国际化语言 + */ +export function getLocale(): string { + return ( + <% if (i18nRouting !== false) {%> + getDetectedLocaleFromPath(locales, defaultLocale) || + <% } %> + new Cookies().get(LOCALE_COOKIE_KEY) || + defaultLocale + ) +} + +function setLocaleToCookies(locale: string) { + const cookies = new Cookies(); + cookies.set(LOCALE_COOKIE_KEY, locale, { path: '/' }); +} + +export function getLocaleFromCookies() { + return new Cookies().get(LOCALE_COOKIE_KEY); +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/types.ts b/packages/plugin-i18n/src/types.ts new file mode 100644 index 0000000000..2ba8183151 --- /dev/null +++ b/packages/plugin-i18n/src/types.ts @@ -0,0 +1,6 @@ +export interface I18nConfig { + locales: string[]; + defaultLocale: string; + redirect?: true; + i18nRouting?: false; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/utils/getDetectedLocaleFromPathname.ts b/packages/plugin-i18n/src/utils/getDetectedLocaleFromPathname.ts new file mode 100644 index 0000000000..9a04681e9a --- /dev/null +++ b/packages/plugin-i18n/src/utils/getDetectedLocaleFromPathname.ts @@ -0,0 +1,34 @@ +import replaceBasename from './replaceBasename'; + +/** + * 开启国际化路由时,通过 pathname 获取当前语言 + */ +function getDetectedLocaleFromPathname( + { + pathname, + locales, + defaultLocale, + basename, + }: { + pathname: string; + locales: string[]; + defaultLocale: string; + basename?: string; + }) { + const normalizedPathname = replaceBasename(pathname, basename); + const pathnameParts = normalizedPathname.split('/').filter(pathnamePart => pathnamePart); + + let detectedLocale = defaultLocale; + + // eslint-disable-next-line no-restricted-syntax + for (const locale of locales) { + if (pathnameParts[0] === locale) { + detectedLocale = locale; + break; + } + } + + return detectedLocale; +} + +export default getDetectedLocaleFromPathname; diff --git a/packages/plugin-i18n/src/utils/getLocaleData.ts b/packages/plugin-i18n/src/utils/getLocaleData.ts new file mode 100644 index 0000000000..528bfa2fd9 --- /dev/null +++ b/packages/plugin-i18n/src/utils/getLocaleData.ts @@ -0,0 +1,100 @@ +import { pick as acceptLanguagePick } from 'accept-language-parser'; +import Cookies from 'universal-cookie'; +import { LOCALE_COOKIE_KEY } from '../constants'; +import { I18nConfig } from '../types'; +import getDetectedLocaleFromPathname from './getDetectedLocaleFromPathname'; +import replaceBasename from './replaceBasename'; + +interface UrlObject { + pathname: string; + search: string; +} + +export default function getLocaleData({ + url, + i18nConfig, + headers, + basename, +}: { + url: UrlObject, + i18nConfig: I18nConfig, + headers?: Record, + basename?: string, +}) { + const { pathname } = url; + const detectedLocale = getDetectedLocale({ pathname, headers, i18nConfig, basename }); + const redirectUrl = getRedirectUrl(url.pathname, { ...i18nConfig, detectedLocale }, basename); + + return { + detectedLocale, + redirectUrl, + }; +} + +function getDetectedLocale( + { + pathname, + i18nConfig, + headers, + basename, + }: { + pathname: string, + i18nConfig: I18nConfig, + headers?: Record, + basename?: string, + }) { + let cookies; + if (typeof window === 'undefined') { + cookies = (new Cookies(headers.cookie)).getAll(); + } else { + cookies = (new Cookies()).getAll(); + } + + const { defaultLocale, locales, i18nRouting } = i18nConfig; + + const detectedLocale = + getLocaleFromCookie(locales, cookies) || + getPreferredLocale(locales, headers) || + (i18nRouting === false ? undefined : getDetectedLocaleFromPathname({ pathname, locales, basename, defaultLocale })) || + defaultLocale; + + return detectedLocale; +} + +/** + * 开启自动重定向选项时(redirect: true)时,获取下一个要跳转的 url + * 仅在 pathname 为 `/` 或者 `/${basename}` 时重定向 + */ +function getRedirectUrl( + pathname: string, + i18nConfig: I18nConfig & { detectedLocale: string }, + basename?: string, +) { + const { redirect, defaultLocale, detectedLocale, i18nRouting } = i18nConfig; + const normalizedPathname = replaceBasename(pathname, basename); + const isRootPath = normalizedPathname === '/'; + if ( + redirect === true && + i18nRouting !== false && + isRootPath && + defaultLocale !== detectedLocale + ) { + return `/${detectedLocale}`; + } +} + +function getLocaleFromCookie(locales: string[], cookies: Record) { + const iceLocale = cookies[LOCALE_COOKIE_KEY]; + + return locales.find(locale => iceLocale === locale); +} + +function getPreferredLocale(locales: string[], headers?: { [key: string]: string }) { + if (typeof window === 'undefined') { + const acceptLanguageValue = headers?.['accept-language']; + return acceptLanguagePick(locales, acceptLanguageValue); + } else { + const acceptLanguages = window.navigator.languages; + return acceptLanguages.find(acceptLanguage => locales.includes(acceptLanguage)); + } +} diff --git a/packages/plugin-i18n/src/utils/normalizeLocalePath.ts b/packages/plugin-i18n/src/utils/normalizeLocalePath.ts new file mode 100644 index 0000000000..26f464d607 --- /dev/null +++ b/packages/plugin-i18n/src/utils/normalizeLocalePath.ts @@ -0,0 +1,30 @@ +import { I18nConfig } from '../types'; +import replaceBasename from './replaceBasename'; + +function normalizeLocalePath( + pathname: string, + i18nConfig: I18nConfig, + basename?: string, +) { + pathname = replaceBasename(pathname, basename); + const { defaultLocale, locales } = i18nConfig; + const subPaths = pathname.split('/'); + let detectedLocale = defaultLocale; + + // eslint-disable-next-line no-restricted-syntax + for (const locale of locales) { + if (subPaths[1] && subPaths[1] === locale) { + detectedLocale = locale; + subPaths.splice(1, 1); + pathname = subPaths.join('/') || '/'; + break; + } + } + + return { + pathname, + detectedLocale, + }; +} + +export default normalizeLocalePath; diff --git a/packages/plugin-i18n/src/utils/replaceBasename.ts b/packages/plugin-i18n/src/utils/replaceBasename.ts new file mode 100644 index 0000000000..6eed54c9e4 --- /dev/null +++ b/packages/plugin-i18n/src/utils/replaceBasename.ts @@ -0,0 +1,21 @@ +function replaceBasename(pathname: string, basename?: string) { + if (typeof basename !== 'string') { + return pathname; + } + + if (basename[0] !== '/') { + // compatible with no slash. For example: ice -> /ice + basename = `/${basename}`; + } + + if (pathname.startsWith(basename)) { + pathname = pathname.substr(basename.length); + if (!pathname.startsWith('/')) { + pathname = `/${pathname || ''}`; + } + } + + return pathname; +} + +export default replaceBasename; diff --git a/packages/plugin-i18n/tsconfig.json b/packages/plugin-i18n/tsconfig.json new file mode 100644 index 0000000000..79b1d7658e --- /dev/null +++ b/packages/plugin-i18n/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.settings.json", + "compilerOptions": { + "baseUrl": "./", + "rootDir": "src", + "outDir": "lib", + "esModuleInterop": true, + "paths": { + "$ice/i18n": ["./src/_i18n"] + } + } +} diff --git a/packages/plugin-ice-ssr/src/renderPages.ts.ejs b/packages/plugin-ice-ssr/src/renderPages.ts.ejs index 9191bd1498..e9edf50d04 100644 --- a/packages/plugin-ice-ssr/src/renderPages.ts.ejs +++ b/packages/plugin-ice-ssr/src/renderPages.ts.ejs @@ -14,18 +14,24 @@ const routes = []; <% } %> export default async function ssgRender(options) { const { htmlTemplate } = options; - <% if (!disableLoadable) { %> - const loadableStatsPath = join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json'); - const buildConfig = { loadableStatsPath }; - <% } else { %> - const buildConfig = {}; - <% } %> + const buildConfig = { + i18n: <%- JSON.stringify(typeof i18n === 'undefined' ? {} : i18n) %>, + <% if (!disableLoadable) { %> + loadableStatsPath: join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json'), + <% } %> + }; const htmlTemplateContent = htmlTemplate || 'global.__ICE_SERVER_HTML_TEMPLATE__'; const pagesData = []; - const flatRoutes = await getFlatRoutes(routes || []); + + let allRoutes = routes; + if (buildConfig.i18n.locales?.length > 0 && buildConfig.i18n.defaultLocale) { + allRoutes = addRoutesByLocales(routes, buildConfig.i18n.locales, buildConfig.i18n.defaultLocale); + } + + const flatRoutes = await getFlatRoutes(allRoutes); for (const flatRoute of flatRoutes) { - const { path = '', getInitialProps, ...rest } = flatRoute; + const { path = '', getInitialProps, component, ...rest } = flatRoute; const keys = []; (pathToRegexp.default || pathToRegexp)(path, keys); @@ -35,9 +41,15 @@ export default async function ssgRender(options) { } const initialContext = { pathname: path, location: { pathname: path } }; - const { html } = await renderStatic({ htmlTemplateContent, buildConfig, initialContext }); + const { html } = await renderStatic({ + htmlTemplateContent, + buildConfig, + initialContext, + getInitialProps: getInitialProps || component?.getInitialProps, + component, + }); - delete rest.component; + delete flatRoute.component; delete rest.routeWrappers; pagesData.push({ html, path, ...rest }); } @@ -46,8 +58,12 @@ export default async function ssgRender(options) { }; async function getFlatRoutes(routes, parentPath = '') { - return await routes.reduce(async (asyncPrev, route) => { + return await routes.reduce(async (asyncPrev, originRoute) => { let prev = await asyncPrev; + + // must clone new object to avoid using the same route + const route = { ...originRoute }; + const { children, path: currentPath = '', redirect } = route; if (children) { prev = prev.concat(await getFlatRoutes(children, currentPath)); @@ -74,3 +90,24 @@ async function getFlatRoutes(routes, parentPath = '') { return prev; }, Promise.resolve([])); } + +function addRoutesByLocales(originRoutes: any[], locales: string[], defaultLocale: string) { + const modifiedRoutes = [...originRoutes]; + + // the locales which are need to add the prefix to the route(e.g.: /home -> /en-US/home). + const prefixRouteLocales = locales.filter(locale => locale !== defaultLocale); + + originRoutes.forEach((route) => { + const { path, redirect } = route; + if(path && !redirect && typeof path === 'string') { + prefixRouteLocales.forEach((prefixRouteLocale: string) => { + modifiedRoutes.unshift({ + ...route, + path: `/${prefixRouteLocale}${path[0] === '/' ? path :`/${path}`}`, + }); + }); + } + }); + + return modifiedRoutes; +} \ No newline at end of file diff --git a/packages/plugin-ice-ssr/src/server.ts.ejs b/packages/plugin-ice-ssr/src/server.ts.ejs index e091056d71..ef52f66957 100644 --- a/packages/plugin-ice-ssr/src/server.ts.ejs +++ b/packages/plugin-ice-ssr/src/server.ts.ejs @@ -40,7 +40,6 @@ interface GenerateHtml { error?: Error; } - const serverRender = async (context: ServerContext, opts = {}) => { let options; if (context.req) { @@ -62,8 +61,11 @@ const serverRender = async (context: ServerContext, opts = {}) => { // loadable-stats.json will be generated to the build dir in development loadableStatsPath = join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json'); } - const buildConfig = { loadableStatsPath, publicPath }; - + const buildConfig = { + loadableStatsPath, + publicPath, + i18n: <%- JSON.stringify(typeof i18n === 'undefined' ? {} : i18n) %>, + } const htmlTemplateContent = htmlTemplate || 'global.__ICE_SERVER_HTML_TEMPLATE__'; // load module to run before createApp ready @@ -77,7 +79,6 @@ const serverRender = async (context: ServerContext, opts = {}) => { const history = { location: { pathname, search, hash, state: null }, }; - setHistory(history); initialContext = { req, res, @@ -112,15 +113,36 @@ const serverRender = async (context: ServerContext, opts = {}) => { return renderStatic({ htmlTemplateContent, initialContext, initialData, buildConfig }); } -export async function renderStatic({ htmlTemplateContent, initialContext, initialData, buildConfig }) { +export async function renderStatic( + { + htmlTemplateContent, + initialContext, + buildConfig, + initialData, + component, + getInitialProps + }: { + htmlTemplateContent: string; + initialContext: Record; + buildConfig: Record; + initialData?: Record; + component?: any; + getInitialProps?: Function; + } + ) { let error; let html = htmlTemplateContent; + try { + // update history in sever side + const history = { location: initialContext.location }; + setHistory(history); + // get initial data if (!initialData) { const getInitialData = appConfig.app && appConfig.app.getInitialData; if (getInitialData) { - console.log('[SSR]', 'getting initial data of app'); + console.log('[SSR]', 'getting initial data of app', initialContext.pathname); initialData = await getInitialData(initialContext); } } @@ -129,19 +151,26 @@ export async function renderStatic({ htmlTemplateContent, initialContext, initia setInitialData(initialData); // get page initial props - let { component, getInitialProps } = await getComponentByPath(routes, initialContext.pathname); + if (!component) { + const result = await getComponentByPath( + routes, + initialContext.pathname + ); + component = result.component; + getInitialProps = result.getInitialProps; + } if (component && component.load) { const loadedPageComponent = await component.load(); component = loadedPageComponent.default; } let pageInitialProps = {}; if (getInitialProps) { - console.log('[SSR]', 'getting initial props of page component'); + console.log('[SSR]', 'getting initial props of page component', initialContext.pathname); pageInitialProps = await getInitialProps(initialContext); } // generate bundle content and register global variables in html - console.log('[SSR]', 'generating html content'); + console.log('[SSR]', 'generating html content', initialContext.pathname); const { bundleContent, loadableComponentExtractor } = reactAppRendererWithSSR( { @@ -173,7 +202,8 @@ export async function renderStatic({ htmlTemplateContent, initialContext, initia htmlTemplateContent, error, }); - logError('[SSR] generate html template error' + error.message); + logError('[SSR] generate html template error message: ' + error.message); + logError('[SSR] generate html template error stack: ' + error.stack); } return { html, error, redirectUrl: initialContext.url }; diff --git a/packages/plugin-router/src/runtime/Router.tsx b/packages/plugin-router/src/runtime/Router.tsx index 92d35d12ed..3b7608346a 100644 --- a/packages/plugin-router/src/runtime/Router.tsx +++ b/packages/plugin-router/src/runtime/Router.tsx @@ -106,7 +106,6 @@ export function Routes({ routes, fallback }: RoutesProps) { {routes.map((route, id) => { const { children } = route; - if (!children) { if (route.redirect) { const { redirect, ...others } = route; diff --git a/packages/plugin-router/tests/formatRoutes.spec.ts b/packages/plugin-router/tests/formatRoutes.spec.ts index 61115eabf5..975c3ffd94 100644 --- a/packages/plugin-router/tests/formatRoutes.spec.ts +++ b/packages/plugin-router/tests/formatRoutes.spec.ts @@ -81,7 +81,7 @@ describe('format-routes', () => { }) - test('childrens priority', () => { + test('children priority', () => { const routes: IRouterConfig[] = [ { path: '/test', @@ -110,4 +110,30 @@ describe('format-routes', () => { componentName: 'mockComponentC' }); }) + + test('call formatRoutes function more than one times with the same routes', () => { + const routes: IRouterConfig[] = [ + { + path: '/test', + component: mockComponentA as any, + children: [ + { + exact: true, + path: '/plan', + component: mockComponentB as any, + }, + { + path: '/', + component: mockComponentC as any, + }, + ], + }, + ]; + + const formattedRoutes = formatRoutes(routes); + const anotherFormattedRoutes = formatRoutes(routes); + + expect(formattedRoutes[0].children[0].path).toBe('/test/plan'); + expect(anotherFormattedRoutes[0].children[0].path).toBe('/test/plan'); + }) }) \ No newline at end of file diff --git a/packages/react-app-renderer/src/server.tsx b/packages/react-app-renderer/src/server.tsx index 0633abdcbe..5b0362c08f 100644 --- a/packages/react-app-renderer/src/server.tsx +++ b/packages/react-app-renderer/src/server.tsx @@ -53,11 +53,17 @@ function deepClone(config) { if(typeof config !== 'object' || config === null) { return config; } - const ret = {}; - Object.getOwnPropertyNames(config).forEach((key: string) => { - if (Object.prototype.hasOwnProperty.call(config, key)) { - ret[key] = deepClone(config[key]); - } - }); - return ret; + + if (Array.isArray(config)) { + return config.slice(); + } else { + const ret = {}; + Object.getOwnPropertyNames(config).forEach((key: string) => { + if (Object.prototype.hasOwnProperty.call(config, key)) { + ret[key] = deepClone(config[key]); + } + }); + return ret; + } + } diff --git a/tsconfig.json b/tsconfig.json index 3c5249c6d0..2ccfce8caf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -48,6 +48,7 @@ { "path": "packages/plugin-ignore-style" }, { "path": "packages/vite-plugin-ts-checker" }, { "path": "packages/vite-service" }, - { "path": "packages/plugin-jsx-plus" } + { "path": "packages/plugin-jsx-plus" }, + { "path": "packages/plugin-i18n" } ] } diff --git a/yarn.lock b/yarn.lock index 37307dcedf..b8af063f38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3114,6 +3114,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@types/accept-language-parser@^1.5.3": + version "1.5.3" + resolved "https://rg.cnpmjs.org/@types/accept-language-parser/download/@types/accept-language-parser-1.5.3.tgz#462f65a7d3900d0390415fa774e060c601519bdd" + integrity sha512-S8oM29O6nnRC3/+rwYV7GBYIIgNIZ52PCxqBG7OuItq9oATnYWy8FfeLKwvq5F7pIYjeeBSCI7y+l+Z9UEQpVQ== + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.0", "@types/babel__core@^7.1.7": version "7.1.17" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64" @@ -3170,6 +3175,11 @@ dependencies: "@types/node" "*" +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== + "@types/debug@0.0.31": version "0.0.31" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.31.tgz#bac8d8aab6a823e91deb7f79083b2a35fa638f33" @@ -3877,6 +3887,11 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +accept-language-parser@^1.5.0: + version "1.5.0" + resolved "https://rg.cnpmjs.org/accept-language-parser/download/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791" + integrity sha1-iHfFQECo3LWeCgfZwf3kIpgzR5E= + accepts@^1.3.7, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -5791,6 +5806,11 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@0.4.1, cookie@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookies@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" @@ -16438,6 +16458,14 @@ unist-util-visit@^1.1.0: dependencies: unist-util-visit-parents "^2.0.0" +universal-cookie@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-4.0.4.tgz#06e8b3625bf9af049569ef97109b4bb226ad798d" + integrity sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw== + dependencies: + "@types/cookie" "^0.3.3" + cookie "^0.4.0" + universal-env@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/universal-env/-/universal-env-3.3.3.tgz#adb1bf6a3885d99efe78ab969f9dc54c39b3ebe4" From 3313a5a8b6d32abab8eda4accb8d0b969eae7830 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Thu, 13 Jan 2022 13:10:06 +0800 Subject: [PATCH 13/20] chore: optimize api name (#5137) --- packages/build-mpa-config/src/generate/BaseGenerator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/build-mpa-config/src/generate/BaseGenerator.ts b/packages/build-mpa-config/src/generate/BaseGenerator.ts index cf3ef17212..2eb5e937b9 100644 --- a/packages/build-mpa-config/src/generate/BaseGenerator.ts +++ b/packages/build-mpa-config/src/generate/BaseGenerator.ts @@ -71,7 +71,7 @@ export default class BaseGenerator { }; applyMethod('addRenderFile', getTemplate('runApp.ts', framework), `${this.runAppPath}.ts`, renderData); - this.generateLoadRuntimeModules(); + this.generateRuntimeModules(); } public generateEntryFile() { @@ -87,7 +87,7 @@ export default class BaseGenerator { applyMethod('addRenderFile', path.join(__dirname, `../template/${framework}/index.tsx.ejs`), this.entryPath, renderData); } - public generateLoadRuntimeModules() { + public generateRuntimeModules() { const { applyMethod } = this.builtInMethods; ['loadRuntimeModules.ts', 'loadStaticModules.ts'].forEach((templateName) => { applyMethod('addRenderFile', getTemplate(templateName), path.join(this.entryFolder, templateName) From e6fcd4b6aa58f5e0ac30584a7bbbcf535df45312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8B=92=E7=8B=92=E7=A5=9E?= Date: Mon, 17 Jan 2022 10:27:41 +0800 Subject: [PATCH 14/20] fix: swc mode error (#5143) --- packages/build-user-config/CHANGELOG.md | 1 + packages/build-user-config/src/userConfig/swc.js | 4 ++-- packages/plugin-react-app/CHANGELOG.md | 1 + packages/plugin-react-app/src/userConfig/fastRefresh.js | 4 +++- yarn.lock | 9 +-------- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/build-user-config/CHANGELOG.md b/packages/build-user-config/CHANGELOG.md index 343107e1d0..437cd54d96 100644 --- a/packages/build-user-config/CHANGELOG.md +++ b/packages/build-user-config/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.1.0 - [feat] support `--force` to empty cache folder +- [fix] swc use esm type ## 2.0.5 diff --git a/packages/build-user-config/src/userConfig/swc.js b/packages/build-user-config/src/userConfig/swc.js index 795d367c35..9ed3797e1a 100644 --- a/packages/build-user-config/src/userConfig/swc.js +++ b/packages/build-user-config/src/userConfig/swc.js @@ -32,10 +32,10 @@ module.exports = (config, swcOptions, context, { log, getValue }) => { react: reactTransformConfig, legacyDecorator: true }, - externalHelpers: true + externalHelpers: false }, module: { - type: 'commonjs', + type: 'es6', noInterop: false, // webpack will evaluate dynamic import, so there need preserve it ignoreDynamic: true diff --git a/packages/plugin-react-app/CHANGELOG.md b/packages/plugin-react-app/CHANGELOG.md index 15737e081d..64c99f8c9b 100644 --- a/packages/plugin-react-app/CHANGELOG.md +++ b/packages/plugin-react-app/CHANGELOG.md @@ -4,6 +4,7 @@ - [feat] optimize runtime when build - [chore] bump version of `es-module-lexer` +- [fix] adapt swc rule name ## 2.1.4 diff --git a/packages/plugin-react-app/src/userConfig/fastRefresh.js b/packages/plugin-react-app/src/userConfig/fastRefresh.js index d9b72b9e51..ed1fcf81fe 100644 --- a/packages/plugin-react-app/src/userConfig/fastRefresh.js +++ b/packages/plugin-react-app/src/userConfig/fastRefresh.js @@ -34,7 +34,9 @@ module.exports = (config, value, context) => { }, }, }; - config.module.rule('swc').use('swc-loader').tap((options) => deepmerge(options || {}, refreshOptions)); + ['jsx', 'tsx'].forEach((ruleName) => { + config.module.rule(`swc-${ruleName}`).use('swc-loader').tap((options) => deepmerge(options || {}, refreshOptions)); + }); } } }; diff --git a/yarn.lock b/yarn.lock index b8af063f38..a2577a9824 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1116,13 +1116,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" - integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/standalone@^7.16.4": version "7.16.4" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.16.4.tgz#f62a5b14fc0e881668f26739f28bcdaacedd3080" @@ -5806,7 +5799,7 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -cookie@0.4.1, cookie@^0.4.0: +cookie@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== From 597ea21f763b86b120be9526b87b06320deeef3d Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Mon, 17 Jan 2022 10:29:13 +0800 Subject: [PATCH 15/20] Fix: i18n not rediect in ssr (#5140) * fix: i18n not rediect in ssr * feat: update test cases * chore: remove Location typs * chore: log when in dev --- examples/basic-i18n/build.json | 3 +- .../__tests__/addRoutesByLocales.test.ts | 199 ++++++++++++++++++ .../__tests__/getRedirectIndexRoute.test.ts | 177 ++++++++++++++++ packages/plugin-i18n/package.json | 3 +- packages/plugin-i18n/src/index.ts | 43 +--- packages/plugin-i18n/src/middleware.ts | 27 --- packages/plugin-i18n/src/runtime.tsx | 71 +++---- .../plugin-i18n/src/templates/index.tsx.ejs | 4 +- .../src/utils/addRoutesByLocales.ts | 46 ++++ .../plugin-i18n/src/utils/getLocaleData.ts | 4 +- .../src/utils/getRedirectIndexRoute.tsx | 50 +++++ .../plugin-i18n/src/utils/replaceBasename.ts | 3 +- packages/plugin-i18n/tsconfig.json | 3 +- 13 files changed, 509 insertions(+), 124 deletions(-) create mode 100644 packages/plugin-i18n/__tests__/addRoutesByLocales.test.ts create mode 100644 packages/plugin-i18n/__tests__/getRedirectIndexRoute.test.ts delete mode 100644 packages/plugin-i18n/src/middleware.ts create mode 100644 packages/plugin-i18n/src/utils/addRoutesByLocales.ts create mode 100644 packages/plugin-i18n/src/utils/getRedirectIndexRoute.tsx diff --git a/examples/basic-i18n/build.json b/examples/basic-i18n/build.json index d7fa1a93fa..e926e951ac 100644 --- a/examples/basic-i18n/build.json +++ b/examples/basic-i18n/build.json @@ -8,8 +8,7 @@ "en-US" ], "defaultLocale": "zh-CN", - "redirect": false, - "i18nRouting": true + "redirect": true } ] ], diff --git a/packages/plugin-i18n/__tests__/addRoutesByLocales.test.ts b/packages/plugin-i18n/__tests__/addRoutesByLocales.test.ts new file mode 100644 index 0000000000..b52875a031 --- /dev/null +++ b/packages/plugin-i18n/__tests__/addRoutesByLocales.test.ts @@ -0,0 +1,199 @@ +import addRoutesByLocales from '../src/utils/addRoutesByLocales'; + +let Layout = null; +let ComponentA = null; +let ComponentB = null; + +const i18nConfig = { + locales: ['en-US', 'zh-CN'], + defaultLocale: 'zh-CN', +} + +describe('add-routes-by-locales', () => { + test('one level', () => { + const routes = [ + { + path: '/', + exact: true, + component: ComponentA, + }, + { + path: '/b', + exact: true, + component: ComponentB, + }, + ]; + const expectedRoutes = [ + { + path: '/en-US', + exact: true, + component: ComponentA, + }, + { + path: '/en-US/b', + exact: true, + component: ComponentB, + }, + { + path: '/', + exact: true, + component: ComponentA, + }, + { + path: '/b', + exact: true, + component: ComponentB, + }, + ]; + const resultRoutes = addRoutesByLocales(routes, i18nConfig); + expect(resultRoutes).toEqual(expectedRoutes); + }) + + test('two levels', () => { + const routes = [ + { + path: '/', + component: Layout, + children: [ + { + exact: true, + path: '/a', + component: ComponentA, + }, + { + path: '/b', + exact: true, + component: ComponentB, + }, + ], + }, + ]; + const expectedRoutes = [ + { + path: '/en-US', + component: Layout, + children: [ + { + exact: true, + path: '/en-US/a', + component: ComponentA, + }, + { + path: '/en-US/b', + exact: true, + component: ComponentB, + }, + ], + }, + { + path: '/', + component: Layout, + children: [ + { + exact: true, + path: '/a', + component: ComponentA, + }, + { + path: '/b', + exact: true, + component: ComponentB, + }, + ], + }, + ]; + const resultRoutes = addRoutesByLocales(routes, i18nConfig); + + expect(resultRoutes).toEqual(expectedRoutes); + }) + + test('multi levels', () => { + const routes = [ + { + path: '/', + component: Layout, + children: [ + { + path: '/a', + children: [ + { + exact: true, + path: '/a/about', + component: ComponentA, + } + ] + }, + { + path: '/', + exact: true, + children: [ + { + exact: true, + path: '/', + component: ComponentB, + } + ] + }, + ] + }, + ]; + const expectedRoutes = [ + { + path: '/en-US', + component: Layout, + children: [ + { + path: '/en-US/a', + children: [ + { + exact: true, + component: ComponentA, + path: '/en-US/a/about' + } + ] + }, + { + path: '/en-US', + exact: true, + children: [ + { + exact: true, + component: ComponentB, + path: '/en-US' + } + ] + } + ] + }, + { + path: '/', + component: Layout, + children: [ + { + path: '/a', + children: [ + { + exact: true, + path: '/a/about', + component: ComponentA, + } + ] + }, + { + path: '/', + exact: true, + children: [ + { + exact: true, + path: '/', + component: ComponentB, + } + ] + }, + ] + }, + ]; + const resultRoutes = addRoutesByLocales(routes, i18nConfig); + expect(resultRoutes).toEqual(expectedRoutes); + }) +}) \ No newline at end of file diff --git a/packages/plugin-i18n/__tests__/getRedirectIndexRoute.test.ts b/packages/plugin-i18n/__tests__/getRedirectIndexRoute.test.ts new file mode 100644 index 0000000000..44d7bdc61c --- /dev/null +++ b/packages/plugin-i18n/__tests__/getRedirectIndexRoute.test.ts @@ -0,0 +1,177 @@ +import getRedirectIndexRoute from '../src/utils/getRedirectIndexRoute'; + +let Layout = null; +let ChildLayout = null; +let ComponentA = null; +let ComponentB = null; +const i18nConfig = { + locales: ['en-US', 'zh-CN'], + defaultLocale: 'zh-CN', +} +const basename = '/ice'; +describe('get-redirect-index-routes', () => { + test('one level routes', () => { + const routes = [ + { + path: '/', + component: ComponentB + }, + { + path: '/a', + component: ComponentA + } + ]; + const expectedRoute = { + path: '/', + component: expect.any(Function), + exact: true, + } + const resultRoute = getRedirectIndexRoute(routes, i18nConfig, basename); + expect(resultRoute).toMatchObject(expectedRoute); + }); + + test('two level routes', () => { + const routes = [ + { + path: '/en-US', + component: Layout, + children: [ + { + exact: true, + path: '/en-US/a', + component: ComponentA, + }, + { + path: '/en-US/b', + exact: true, + component: ComponentB, + }, + ], + }, + { + path: '/', + component: Layout, + children: [ + { + exact: true, + path: '/', + component: ComponentA, + }, + { + path: '/b', + exact: true, + component: ComponentB, + }, + ], + }, + ]; + const expectedRoute = { + path: '/', + component: Layout, + exact: true, + children: [ + { + exact: true, + path: '/', + component: expect.any(Function), + }, + ], + }; + const resultRoute = getRedirectIndexRoute(routes, i18nConfig, basename); + expect(resultRoute).toMatchObject(expectedRoute); + }); + + test('three level routes', () => { + const routes = [ + { + path: '/en-US', + component: Layout, + children: [ + { + path: '/en-US/a', + children: [ + { + exact: true, + component: ComponentA, + path: '/en-US/a/about' + } + ] + }, + { + path: '/en-US', + exact: true, + children: [ + { + exact: true, + component: ComponentB, + path: '/en-US' + } + ] + } + ] + }, + { + path: '/', + component: Layout, + children: [ + { + path: '/a', + children: [ + { + exact: true, + path: '/a/about', + component: ComponentA, + } + ] + }, + { + path: '/', + exact: true, + children: [ + { + exact: true, + path: '/', + component: ComponentB, + } + ] + }, + ] + }, + ]; + const expectedRoute = { + path: '/', + component: Layout, + exact: true, + children: [ + { + exact: true, + path: '/', + children: [ + { + exact: true, + path: '/', + component: expect.any(Function), + } + ] + }, + ], + }; + const resultRoute = getRedirectIndexRoute(routes, i18nConfig, basename); + expect(resultRoute).toMatchObject(expectedRoute); + }); + + test('should receive undefined', () => { + const routes = [ + { + path: '/b', + component: ComponentB + }, + { + path: '/a', + component: ComponentA + } + ]; + const resultRoute = getRedirectIndexRoute(routes, i18nConfig, basename); + expect(resultRoute).toBeUndefined(); + }) +}) \ No newline at end of file diff --git a/packages/plugin-i18n/package.json b/packages/plugin-i18n/package.json index 492e50a855..5202647719 100644 --- a/packages/plugin-i18n/package.json +++ b/packages/plugin-i18n/package.json @@ -19,7 +19,8 @@ }, "peerDependencies": { "react": ">=16.0.0", - "react-dom": ">=16.0.0" + "react-dom": ">=16.0.0", + "react-router-dom": "^5.0.0" }, "devDependencies": { "build-scripts": "^1.2.1", diff --git a/packages/plugin-i18n/src/index.ts b/packages/plugin-i18n/src/index.ts index b83223a239..752519bd34 100644 --- a/packages/plugin-i18n/src/index.ts +++ b/packages/plugin-i18n/src/index.ts @@ -2,17 +2,14 @@ import { IPluginAPI } from 'build-scripts'; import path from 'path'; import { I18nConfig } from './types'; import { LOCALE_COOKIE_KEY } from './constants'; -import { expressI18nMiddleware } from './middleware'; export default async function ( - { onGetWebpackConfig, getValue, applyMethod, context }: IPluginAPI, + { onGetWebpackConfig, getValue, applyMethod }: IPluginAPI, i18nConfig: I18nConfig, ) { checkI18nConfig(i18nConfig); - const { i18nRouting = true, redirect = false } = i18nConfig; - - const { userConfig: { ssr } } = context; + const { i18nRouting = true } = i18nConfig; const iceTemp = getValue('TEMP_PATH'); const i18nTempDir = path.join(iceTemp, 'plugins', 'i18n'); @@ -37,42 +34,6 @@ export default async function ( onGetWebpackConfig((config) => { config.resolve.alias.set('$ice/i18n', path.join(i18nTempDir, 'index.tsx')); - - const onBeforeSetupMiddleware = config.devServer.get('onBeforeSetupMiddleware'); // webpack 5 - const originalDevServeBefore = onBeforeSetupMiddleware || config.devServer.get('before'); - - function devServerBefore(...args: any[]) { - const [devServer] = args; - let app; - if (onBeforeSetupMiddleware) { - app = devServer.app; - } else { - app = devServer; - } - - const pattern = /^\/?((?!\.(js|css|map|json|png|jpg|jpeg|gif|svg|eot|woff2|ttf|ico)).)*$/; - app.get(pattern, expressI18nMiddleware(i18nConfig)); - - if (typeof originalDevServeBefore === 'function') { - originalDevServeBefore(...args); - } - } - - if (ssr && redirect) { - if (onBeforeSetupMiddleware) { - config.merge({ - devServer: { - onBeforeSetupMiddleware: devServerBefore, - } - }); - } else { - config.merge({ - devServer: { - before: devServerBefore, - } - }); - } - } }); // export API diff --git a/packages/plugin-i18n/src/middleware.ts b/packages/plugin-i18n/src/middleware.ts deleted file mode 100644 index b569969707..0000000000 --- a/packages/plugin-i18n/src/middleware.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { I18nConfig } from './types'; -import getLocaleData from './utils/getLocaleData'; - -export function expressI18nMiddleware(i18nConfig: I18nConfig, basename?: string) { - return function (req, res, next) { - const { headers, _parsedUrl } = req; - const { redirectUrl } = getLocaleData({ headers, url: _parsedUrl, i18nConfig, basename }); - if (redirectUrl) { - res.redirect(307, redirectUrl); - } else { - next(); - } - }; -} - -export function koaI18nMiddleware(i18nConfig: I18nConfig, basename?: string) { - return async function (ctx, next) { - const { req, res } = ctx; - const { headers, _parsedUrl } = req; - const { redirectUrl } = getLocaleData({ headers, url: _parsedUrl, i18nConfig, basename }); - - if (redirectUrl) { - res.redirect(307, redirectUrl); - } - await next(); - }; -} \ No newline at end of file diff --git a/packages/plugin-i18n/src/runtime.tsx b/packages/plugin-i18n/src/runtime.tsx index d38f7b4d94..42432155ae 100644 --- a/packages/plugin-i18n/src/runtime.tsx +++ b/packages/plugin-i18n/src/runtime.tsx @@ -6,32 +6,39 @@ import { LOCALE_COOKIE_KEY } from './constants'; import getLocaleData from './utils/getLocaleData'; import { I18nConfig } from './types'; import normalizeLocalePath from './utils/normalizeLocalePath'; +import addRoutesByLocales from './utils/addRoutesByLocales'; +import getRedirectIndexRoute from './utils/getRedirectIndexRoute'; export default ({ modifyRoutes, buildConfig, addProvider, appConfig }) => { const { i18n: i18nConfig } = buildConfig; - const { defaultLocale, locales, i18nRouting } = i18nConfig; + const { i18nRouting, redirect } = i18nConfig; const { router: appConfigRouter = {} } = appConfig; const { history = {}, basename } = appConfigRouter; - const originHistory = { ...history }; if (i18nRouting !== false) { modifyRoutes((routes) => { - // 该 routes 值是被 formatRoutes 方法处理后返回的结果 - return addRoutesByLocales(routes, locales, defaultLocale); + // routes 值是被 formatRoutes 方法处理后返回的结果 + const modifiedRoutes = addRoutesByLocales(routes, i18nConfig); + + if (redirect === true) { + const newIndexComponent = getRedirectIndexRoute(modifiedRoutes, i18nConfig, basename); + if (newIndexComponent) { + modifiedRoutes.unshift(newIndexComponent); + } + } + if (process.env.NODE_ENV !== 'production') { + console.log('[build-plugin-ice-i18n] originRoutes: ', routes); + console.log('[build-plugin-ice-i18n] modifiedRoutes: ', modifiedRoutes); + } + return modifiedRoutes; }); } addProvider(Provider()); if (!process.env.__IS_SERVER__) { - const { redirectUrl, detectedLocale } = getLocaleData({ url: window.location, i18nConfig, basename }); - + const { detectedLocale } = getLocaleData({ url: window.location, i18nConfig, basename }); setInitICELocaleToCookie(detectedLocale); - - if (redirectUrl) { - console.log(`[icejs locale plugin]: redirect to ${redirectUrl}`); - originHistory.push(redirectUrl); - } } if (i18nRouting !== false) { @@ -39,36 +46,6 @@ export default ({ modifyRoutes, buildConfig, addProvider, appConfig }) => { } }; -function addRoutesByLocales(originRoutes: any[], locales: string[], defaultLocale: string) { - // the locales which are need to add the prefix to the route(e.g.: /home -> /en-US/home). - const prefixRouteLocales = locales.filter(locale => locale !== defaultLocale); - - if (!prefixRouteLocales.length) { - return originRoutes; - } - const modifiedRoutes = [...originRoutes]; - - prefixRouteLocales.forEach((prefixRouteLocale: string) => { - originRoutes.forEach((originRoute) => { - const { children, path } = originRoute; - if (!children) { - modifiedRoutes.unshift({ - ...originRoute, - path: `/${prefixRouteLocale}${path[0] === '/' ? path :`/${path}`}`, - }); - } else { - modifiedRoutes.unshift({ - ...originRoute, - path: `/${prefixRouteLocale}${path[0] === '/' ? path :`/${path}`}`, - children: addRoutesByLocales(children, locales, defaultLocale), - }); - } - }); - }); - - return modifiedRoutes; -} - function Provider() { return function({ children }) { return ( @@ -92,15 +69,15 @@ function modifyHistory(history: History, i18nConfig: I18nConfig, basename?: stri const { defaultLocale } = i18nConfig; function getLocalePath( - pathname: string, + path: string, locale: string, ) { - const localePathResult = normalizeLocalePath(pathname, i18nConfig, basename); - + const localePathResult = normalizeLocalePath(path, i18nConfig, basename); + const { pathname } = localePathResult; if (locale === defaultLocale) { - return localePathResult.pathname; + return pathname; } - return `/${locale}${localePathResult.pathname === '/' ? '' : localePathResult.pathname}`; + return `/${locale}${pathname === '/' ? '' : pathname}`; } history.push = function(path: string, state?: unknown) { @@ -114,4 +91,4 @@ function modifyHistory(history: History, i18nConfig: I18nConfig, basename?: stri const localePath = getLocalePath(path, locale); originHistory.replace(localePath, state); }; -} \ No newline at end of file +} diff --git a/packages/plugin-i18n/src/templates/index.tsx.ejs b/packages/plugin-i18n/src/templates/index.tsx.ejs index b1307d21d2..a3bcc4ef35 100644 --- a/packages/plugin-i18n/src/templates/index.tsx.ejs +++ b/packages/plugin-i18n/src/templates/index.tsx.ejs @@ -56,7 +56,9 @@ export function getAllLocales(): string[] { function getDetectedLocaleFromPath(locales: string[], defaultLocale: string) { const appConfig = getAppConfig(); if (history === null) { - console.log('history is null, can\'t get current locale from pathname'); + if (process.env.NODE_ENV !== 'production') { + console.log('history is null, can\'t get current locale from pathname'); + } return; } const { location: { pathname } } = history; diff --git a/packages/plugin-i18n/src/utils/addRoutesByLocales.ts b/packages/plugin-i18n/src/utils/addRoutesByLocales.ts new file mode 100644 index 0000000000..f85001a1d5 --- /dev/null +++ b/packages/plugin-i18n/src/utils/addRoutesByLocales.ts @@ -0,0 +1,46 @@ +import { I18nConfig } from '../types'; + +/** + * 在开启国际化语言后,添加国际化路由 + * + * 比如:locales: ['en-US', 'zh-CN', 'en-UK'], defaultLocale: 'zh-CN' + * /home 路由会生成 /en-US/home /en-UK/home + */ +function addRoutesByLocales(originRoutes: any[], i18nConfig: I18nConfig) { + const { locales, defaultLocale } = i18nConfig; + // the locales which are need to add the prefix to the route(e.g.: /home -> /en-US/home). + const prefixRouteLocales = locales.filter(locale => locale !== defaultLocale); + + if (!prefixRouteLocales.length) { + return originRoutes; + } + const modifiedRoutes = [...originRoutes]; + + prefixRouteLocales.forEach((prefixRouteLocale: string) => { + function addRoutesByLocale(routes) { + const result = routes.map((route) => { + const { children, path } = route; + if (!children) { + return { + ...route, + path: `/${prefixRouteLocale}${path === '/' ? '' : `${path[0] === '/' ? '' : '/'}${path}`}`, + }; + } else { + return { + ...route, + path: `/${prefixRouteLocale}${path === '/' ? '' : `${path[0] === '/' ? '' : '/'}${path}`}`, + children: addRoutesByLocale(children), + }; + } + }); + + return result; + } + + modifiedRoutes.unshift(...addRoutesByLocale(originRoutes)); + }); + + return modifiedRoutes; +} + +export default addRoutesByLocales; \ No newline at end of file diff --git a/packages/plugin-i18n/src/utils/getLocaleData.ts b/packages/plugin-i18n/src/utils/getLocaleData.ts index 528bfa2fd9..d53b94903e 100644 --- a/packages/plugin-i18n/src/utils/getLocaleData.ts +++ b/packages/plugin-i18n/src/utils/getLocaleData.ts @@ -21,9 +21,9 @@ export default function getLocaleData({ headers?: Record, basename?: string, }) { - const { pathname } = url; + const pathname = url.pathname || '/'; const detectedLocale = getDetectedLocale({ pathname, headers, i18nConfig, basename }); - const redirectUrl = getRedirectUrl(url.pathname, { ...i18nConfig, detectedLocale }, basename); + const redirectUrl = getRedirectUrl(pathname, { ...i18nConfig, detectedLocale }, basename); return { detectedLocale, diff --git a/packages/plugin-i18n/src/utils/getRedirectIndexRoute.tsx b/packages/plugin-i18n/src/utils/getRedirectIndexRoute.tsx new file mode 100644 index 0000000000..03b724144d --- /dev/null +++ b/packages/plugin-i18n/src/utils/getRedirectIndexRoute.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { Redirect } from 'react-router-dom'; +import { I18nConfig } from '../types'; +import getLocaleData from './getLocaleData'; + +/** + * 在开启路由重定向时,生成根路由 redirect 组件 + */ +export default function getRedirectIndexRoute(originRoutes: any[], i18nConfig: I18nConfig, basename?: string) { + function walkRoute(routes: any[]) { + // eslint-disable-next-line no-restricted-syntax + for (const route of routes) { + const { path, children, ...rest } = route; + if (path === '/') { + if (children) { + const result = walkRoute(children); + if (result) { + return { + ...rest, + path, + exact: true, + children: [result], + }; + } + } else { + return { + ...route, + exact: true, + component: (props: any) => ( + + ) + }; + } + } + } + } + + return walkRoute(originRoutes); +} + +export function IndexComponent(props) { + const { i18nConfig, basename, OriginIndexComponent, location, staticContext, ...restProps } = props; + const { redirectUrl } = getLocaleData({ url: location, i18nConfig, basename, headers: staticContext?.req?.headers }); + return redirectUrl ? : ; +} \ No newline at end of file diff --git a/packages/plugin-i18n/src/utils/replaceBasename.ts b/packages/plugin-i18n/src/utils/replaceBasename.ts index 6eed54c9e4..dcd865a966 100644 --- a/packages/plugin-i18n/src/utils/replaceBasename.ts +++ b/packages/plugin-i18n/src/utils/replaceBasename.ts @@ -2,14 +2,13 @@ function replaceBasename(pathname: string, basename?: string) { if (typeof basename !== 'string') { return pathname; } - if (basename[0] !== '/') { // compatible with no slash. For example: ice -> /ice basename = `/${basename}`; } if (pathname.startsWith(basename)) { - pathname = pathname.substr(basename.length); + pathname = pathname.substring(basename.length); if (!pathname.startsWith('/')) { pathname = `/${pathname || ''}`; } diff --git a/packages/plugin-i18n/tsconfig.json b/packages/plugin-i18n/tsconfig.json index 79b1d7658e..541ec251ed 100644 --- a/packages/plugin-i18n/tsconfig.json +++ b/packages/plugin-i18n/tsconfig.json @@ -8,5 +8,6 @@ "paths": { "$ice/i18n": ["./src/_i18n"] } - } + }, + "exclude": ["__tests__"] } From 9e2e81a428ab8df39c0dcba4ef86b89257f2797e Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Mon, 17 Jan 2022 10:56:13 +0800 Subject: [PATCH 16/20] Fix: store not work in mpa(webpack mode) (#5144) * fix: store not work in mpa * feat: add mpa-webpack example * chore: example name * chore: update comment * fix: use rootDir to replace process.cwd() * chore: format --- .../{basic-mpa => basic-mpa-vite}/build.json | 0 .../package.json | 2 +- .../public/index.html | 0 .../public/web.html | 0 .../sandbox.config.json | 0 .../src/pages/About/app.tsx | 0 .../src/pages/About/index.tsx | 0 .../src/pages/Dashboard/app.tsx | 0 .../src/pages/Dashboard/index.tsx | 0 .../src/pages/Dashboard/models/counter.ts | 0 .../src/pages/Dashboard/routes.ts | 0 .../src/pages/Dashboard/store.ts | 0 .../src/pages/Detail/index.tsx | 0 .../src/pages/Home/index.tsx | 0 .../src/pages/Home/model.ts | 0 .../src/pages/Home/store.ts | 0 .../src/pages/Profile/app.tsx | 0 .../src/pages/Profile/index.tsx | 0 .../src/pages/Profile/models/counter.ts | 0 .../src/pages/Profile/store.ts | 0 .../tsconfig.json | 0 examples/basic-mpa-webpack/build.json | 19 ++++++ examples/basic-mpa-webpack/package.json | 19 ++++++ examples/basic-mpa-webpack/public/index.html | 13 ++++ examples/basic-mpa-webpack/public/web.html | 14 +++++ .../basic-mpa-webpack/sandbox.config.json | 6 ++ .../basic-mpa-webpack/src/pages/About/app.tsx | 11 ++++ .../src/pages/About/index.tsx | 11 ++++ .../src/pages/Dashboard/app.tsx | 19 ++++++ .../src/pages/Dashboard/index.tsx | 18 ++++++ .../src/pages/Dashboard/models/counter.ts | 23 +++++++ .../src/pages/Dashboard/routes.ts | 3 + .../src/pages/Dashboard/store.ts | 6 ++ .../src/pages/Detail/index.tsx | 4 ++ .../src/pages/Home/index.tsx | 14 +++++ .../basic-mpa-webpack/src/pages/Home/model.ts | 5 ++ .../basic-mpa-webpack/src/pages/Home/store.ts | 6 ++ .../src/pages/Profile/app.tsx | 18 ++++++ .../src/pages/Profile/index.tsx | 18 ++++++ .../src/pages/Profile/models/counter.ts | 14 +++++ .../src/pages/Profile/store.ts | 6 ++ examples/basic-mpa-webpack/tsconfig.json | 41 ++++++++++++ examples/basic-mpa-with-swc/package.json | 2 +- .../src/babelPluginReplacePath.ts | 63 ++++++++++++------- ...sic-mpa.test.ts => basic-mpa-vite.test.ts} | 2 +- test/basic-mpa-webpack.test.ts | 34 ++++++++++ 46 files changed, 365 insertions(+), 26 deletions(-) rename examples/{basic-mpa => basic-mpa-vite}/build.json (100%) rename examples/{basic-mpa => basic-mpa-vite}/package.json (92%) rename examples/{basic-mpa => basic-mpa-vite}/public/index.html (100%) rename examples/{basic-mpa => basic-mpa-vite}/public/web.html (100%) rename examples/{basic-mpa => basic-mpa-vite}/sandbox.config.json (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/About/app.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/About/index.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Dashboard/app.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Dashboard/index.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Dashboard/models/counter.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Dashboard/routes.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Dashboard/store.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Detail/index.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Home/index.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Home/model.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Home/store.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Profile/app.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Profile/index.tsx (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Profile/models/counter.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/src/pages/Profile/store.ts (100%) rename examples/{basic-mpa => basic-mpa-vite}/tsconfig.json (100%) create mode 100644 examples/basic-mpa-webpack/build.json create mode 100644 examples/basic-mpa-webpack/package.json create mode 100644 examples/basic-mpa-webpack/public/index.html create mode 100644 examples/basic-mpa-webpack/public/web.html create mode 100644 examples/basic-mpa-webpack/sandbox.config.json create mode 100644 examples/basic-mpa-webpack/src/pages/About/app.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/About/index.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Dashboard/app.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Dashboard/index.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Dashboard/models/counter.ts create mode 100644 examples/basic-mpa-webpack/src/pages/Dashboard/routes.ts create mode 100644 examples/basic-mpa-webpack/src/pages/Dashboard/store.ts create mode 100644 examples/basic-mpa-webpack/src/pages/Detail/index.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Home/index.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Home/model.ts create mode 100644 examples/basic-mpa-webpack/src/pages/Home/store.ts create mode 100644 examples/basic-mpa-webpack/src/pages/Profile/app.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Profile/index.tsx create mode 100644 examples/basic-mpa-webpack/src/pages/Profile/models/counter.ts create mode 100644 examples/basic-mpa-webpack/src/pages/Profile/store.ts create mode 100644 examples/basic-mpa-webpack/tsconfig.json rename test/{basic-mpa.test.ts => basic-mpa-vite.test.ts} (96%) create mode 100644 test/basic-mpa-webpack.test.ts diff --git a/examples/basic-mpa/build.json b/examples/basic-mpa-vite/build.json similarity index 100% rename from examples/basic-mpa/build.json rename to examples/basic-mpa-vite/build.json diff --git a/examples/basic-mpa/package.json b/examples/basic-mpa-vite/package.json similarity index 92% rename from examples/basic-mpa/package.json rename to examples/basic-mpa-vite/package.json index 48d5916f75..d617cc34a4 100644 --- a/examples/basic-mpa/package.json +++ b/examples/basic-mpa-vite/package.json @@ -1,5 +1,5 @@ { - "name": "exmaple-basic-mpa", + "name": "basic-mpa-vite", "description": "", "dependencies": { "react": "^16.4.1", diff --git a/examples/basic-mpa/public/index.html b/examples/basic-mpa-vite/public/index.html similarity index 100% rename from examples/basic-mpa/public/index.html rename to examples/basic-mpa-vite/public/index.html diff --git a/examples/basic-mpa/public/web.html b/examples/basic-mpa-vite/public/web.html similarity index 100% rename from examples/basic-mpa/public/web.html rename to examples/basic-mpa-vite/public/web.html diff --git a/examples/basic-mpa/sandbox.config.json b/examples/basic-mpa-vite/sandbox.config.json similarity index 100% rename from examples/basic-mpa/sandbox.config.json rename to examples/basic-mpa-vite/sandbox.config.json diff --git a/examples/basic-mpa/src/pages/About/app.tsx b/examples/basic-mpa-vite/src/pages/About/app.tsx similarity index 100% rename from examples/basic-mpa/src/pages/About/app.tsx rename to examples/basic-mpa-vite/src/pages/About/app.tsx diff --git a/examples/basic-mpa/src/pages/About/index.tsx b/examples/basic-mpa-vite/src/pages/About/index.tsx similarity index 100% rename from examples/basic-mpa/src/pages/About/index.tsx rename to examples/basic-mpa-vite/src/pages/About/index.tsx diff --git a/examples/basic-mpa/src/pages/Dashboard/app.tsx b/examples/basic-mpa-vite/src/pages/Dashboard/app.tsx similarity index 100% rename from examples/basic-mpa/src/pages/Dashboard/app.tsx rename to examples/basic-mpa-vite/src/pages/Dashboard/app.tsx diff --git a/examples/basic-mpa/src/pages/Dashboard/index.tsx b/examples/basic-mpa-vite/src/pages/Dashboard/index.tsx similarity index 100% rename from examples/basic-mpa/src/pages/Dashboard/index.tsx rename to examples/basic-mpa-vite/src/pages/Dashboard/index.tsx diff --git a/examples/basic-mpa/src/pages/Dashboard/models/counter.ts b/examples/basic-mpa-vite/src/pages/Dashboard/models/counter.ts similarity index 100% rename from examples/basic-mpa/src/pages/Dashboard/models/counter.ts rename to examples/basic-mpa-vite/src/pages/Dashboard/models/counter.ts diff --git a/examples/basic-mpa/src/pages/Dashboard/routes.ts b/examples/basic-mpa-vite/src/pages/Dashboard/routes.ts similarity index 100% rename from examples/basic-mpa/src/pages/Dashboard/routes.ts rename to examples/basic-mpa-vite/src/pages/Dashboard/routes.ts diff --git a/examples/basic-mpa/src/pages/Dashboard/store.ts b/examples/basic-mpa-vite/src/pages/Dashboard/store.ts similarity index 100% rename from examples/basic-mpa/src/pages/Dashboard/store.ts rename to examples/basic-mpa-vite/src/pages/Dashboard/store.ts diff --git a/examples/basic-mpa/src/pages/Detail/index.tsx b/examples/basic-mpa-vite/src/pages/Detail/index.tsx similarity index 100% rename from examples/basic-mpa/src/pages/Detail/index.tsx rename to examples/basic-mpa-vite/src/pages/Detail/index.tsx diff --git a/examples/basic-mpa/src/pages/Home/index.tsx b/examples/basic-mpa-vite/src/pages/Home/index.tsx similarity index 100% rename from examples/basic-mpa/src/pages/Home/index.tsx rename to examples/basic-mpa-vite/src/pages/Home/index.tsx diff --git a/examples/basic-mpa/src/pages/Home/model.ts b/examples/basic-mpa-vite/src/pages/Home/model.ts similarity index 100% rename from examples/basic-mpa/src/pages/Home/model.ts rename to examples/basic-mpa-vite/src/pages/Home/model.ts diff --git a/examples/basic-mpa/src/pages/Home/store.ts b/examples/basic-mpa-vite/src/pages/Home/store.ts similarity index 100% rename from examples/basic-mpa/src/pages/Home/store.ts rename to examples/basic-mpa-vite/src/pages/Home/store.ts diff --git a/examples/basic-mpa/src/pages/Profile/app.tsx b/examples/basic-mpa-vite/src/pages/Profile/app.tsx similarity index 100% rename from examples/basic-mpa/src/pages/Profile/app.tsx rename to examples/basic-mpa-vite/src/pages/Profile/app.tsx diff --git a/examples/basic-mpa/src/pages/Profile/index.tsx b/examples/basic-mpa-vite/src/pages/Profile/index.tsx similarity index 100% rename from examples/basic-mpa/src/pages/Profile/index.tsx rename to examples/basic-mpa-vite/src/pages/Profile/index.tsx diff --git a/examples/basic-mpa/src/pages/Profile/models/counter.ts b/examples/basic-mpa-vite/src/pages/Profile/models/counter.ts similarity index 100% rename from examples/basic-mpa/src/pages/Profile/models/counter.ts rename to examples/basic-mpa-vite/src/pages/Profile/models/counter.ts diff --git a/examples/basic-mpa/src/pages/Profile/store.ts b/examples/basic-mpa-vite/src/pages/Profile/store.ts similarity index 100% rename from examples/basic-mpa/src/pages/Profile/store.ts rename to examples/basic-mpa-vite/src/pages/Profile/store.ts diff --git a/examples/basic-mpa/tsconfig.json b/examples/basic-mpa-vite/tsconfig.json similarity index 100% rename from examples/basic-mpa/tsconfig.json rename to examples/basic-mpa-vite/tsconfig.json diff --git a/examples/basic-mpa-webpack/build.json b/examples/basic-mpa-webpack/build.json new file mode 100644 index 0000000000..cad3d2a6c9 --- /dev/null +++ b/examples/basic-mpa-webpack/build.json @@ -0,0 +1,19 @@ +{ + "mpa": { + "openPage": "Dashboard", + "template": { + "web.html": [ + "Dashboard", + "Home" + ] + }, + "rewrites": { + "dashboard": "site/dashboard" + } + }, + "devServer": { + "devMiddleware": { + "writeToDisk": true + } + } +} \ No newline at end of file diff --git a/examples/basic-mpa-webpack/package.json b/examples/basic-mpa-webpack/package.json new file mode 100644 index 0000000000..3bbce0347f --- /dev/null +++ b/examples/basic-mpa-webpack/package.json @@ -0,0 +1,19 @@ +{ + "name": "basic-mpa-webpack", + "description": "", + "dependencies": { + "react": "^16.4.1", + "react-dom": "^16.4.1" + }, + "devDependencies": { + "@types/react": "^16.9.13", + "@types/react-dom": "^16.9.4" + }, + "scripts": { + "start": "../../packages/icejs/bin/ice-cli.js start", + "build": "../../packages/icejs/bin/ice-cli.js build" + }, + "engines": { + "node": ">=8.0.0" + } +} diff --git a/examples/basic-mpa-webpack/public/index.html b/examples/basic-mpa-webpack/public/index.html new file mode 100644 index 0000000000..0712c8389b --- /dev/null +++ b/examples/basic-mpa-webpack/public/index.html @@ -0,0 +1,13 @@ + + + + + + + icejs · mpa example + + +
current page: <%= pageName %>
+
+ + diff --git a/examples/basic-mpa-webpack/public/web.html b/examples/basic-mpa-webpack/public/web.html new file mode 100644 index 0000000000..6adfc30490 --- /dev/null +++ b/examples/basic-mpa-webpack/public/web.html @@ -0,0 +1,14 @@ + + + + + + + icejs · mpa example + + + +
using web.html template
+
+ + diff --git a/examples/basic-mpa-webpack/sandbox.config.json b/examples/basic-mpa-webpack/sandbox.config.json new file mode 100644 index 0000000000..19b69053c1 --- /dev/null +++ b/examples/basic-mpa-webpack/sandbox.config.json @@ -0,0 +1,6 @@ +{ + "template": "node", + "container": { + "port": 3333 + } +} diff --git a/examples/basic-mpa-webpack/src/pages/About/app.tsx b/examples/basic-mpa-webpack/src/pages/About/app.tsx new file mode 100644 index 0000000000..82484d9c64 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/About/app.tsx @@ -0,0 +1,11 @@ +import { runApp, IAppConfig } from 'ice'; +import Index from './index'; + +const appConfig: IAppConfig = { + router: { + type: 'hash', + routes: [{ path: '/', component: Index }], + }, +}; + +runApp(appConfig); diff --git a/examples/basic-mpa-webpack/src/pages/About/index.tsx b/examples/basic-mpa-webpack/src/pages/About/index.tsx new file mode 100644 index 0000000000..ea759ec365 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/About/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const About = () => { + return ( + <> +

About Page...

+ + ); +}; + +export default About; diff --git a/examples/basic-mpa-webpack/src/pages/Dashboard/app.tsx b/examples/basic-mpa-webpack/src/pages/Dashboard/app.tsx new file mode 100644 index 0000000000..97d6cf42e2 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Dashboard/app.tsx @@ -0,0 +1,19 @@ +import { runApp, IAppConfig } from 'ice'; +import React from 'react'; +import routes from './routes'; +import store from './store'; + +const Provider = store.Provider; + +const appConfig: IAppConfig = { + router: { + routes, + }, + app: { + addProvider({ children }) { + return {children}; + } + } +}; + +runApp(appConfig); diff --git a/examples/basic-mpa-webpack/src/pages/Dashboard/index.tsx b/examples/basic-mpa-webpack/src/pages/Dashboard/index.tsx new file mode 100644 index 0000000000..eb87ce2dad --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Dashboard/index.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import store from './store'; + +const Dashboard = () => { + const [pageState, pageActions] = store.useModel('counter'); + return ( + <> +

Dashboard Page...

+
+ + {pageState.count} + +
+ + ); +}; + +export default Dashboard; diff --git a/examples/basic-mpa-webpack/src/pages/Dashboard/models/counter.ts b/examples/basic-mpa-webpack/src/pages/Dashboard/models/counter.ts new file mode 100644 index 0000000000..e278efcb86 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Dashboard/models/counter.ts @@ -0,0 +1,23 @@ +export const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time)); + +export default { + state: { + count: 0 + }, + + reducers: { + increment (prevState) { + return { count: prevState.count + 1 }; + }, + decrement (prevState) { + return { count: prevState.count - 1 }; + } + }, + + effects: (dispatch) => ({ + async decrementAsync () { + await delay(10); + dispatch.counter.decrement(); + }, + }), +}; diff --git a/examples/basic-mpa-webpack/src/pages/Dashboard/routes.ts b/examples/basic-mpa-webpack/src/pages/Dashboard/routes.ts new file mode 100644 index 0000000000..46305cea89 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Dashboard/routes.ts @@ -0,0 +1,3 @@ +import Index from './index'; + +export default [{ path: '/', component: Index }]; diff --git a/examples/basic-mpa-webpack/src/pages/Dashboard/store.ts b/examples/basic-mpa-webpack/src/pages/Dashboard/store.ts new file mode 100644 index 0000000000..fd399d4f11 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Dashboard/store.ts @@ -0,0 +1,6 @@ +import { createStore } from 'ice'; +import counter from './models/counter'; + +const store = createStore({ counter }); + +export default store; diff --git a/examples/basic-mpa-webpack/src/pages/Detail/index.tsx b/examples/basic-mpa-webpack/src/pages/Detail/index.tsx new file mode 100644 index 0000000000..2b565683f0 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Detail/index.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +ReactDOM.render(

Detail Page

, document.body); diff --git a/examples/basic-mpa-webpack/src/pages/Home/index.tsx b/examples/basic-mpa-webpack/src/pages/Home/index.tsx new file mode 100644 index 0000000000..5278f628b7 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Home/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import store from './store'; + +const Home = () => { + const [state] = store.useModel('default'); + + return ( + <> +

{state.title}

+ + ); +}; + +export default () => (); diff --git a/examples/basic-mpa-webpack/src/pages/Home/model.ts b/examples/basic-mpa-webpack/src/pages/Home/model.ts new file mode 100644 index 0000000000..bac3d3427b --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Home/model.ts @@ -0,0 +1,5 @@ +export default { + state: { + title: 'Home Page' + }, +}; diff --git a/examples/basic-mpa-webpack/src/pages/Home/store.ts b/examples/basic-mpa-webpack/src/pages/Home/store.ts new file mode 100644 index 0000000000..3b6c2759d8 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Home/store.ts @@ -0,0 +1,6 @@ +import { createStore } from 'ice'; +import model from './model'; + +const store = createStore({ default: model }); + +export default store; diff --git a/examples/basic-mpa-webpack/src/pages/Profile/app.tsx b/examples/basic-mpa-webpack/src/pages/Profile/app.tsx new file mode 100644 index 0000000000..c383d9529f --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Profile/app.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { runApp, IAppConfig } from 'ice'; +import Page from './index'; +import store from './store'; + +const Provider = store.Provider; + +const appConfig: IAppConfig = { + app: { + rootId: 'custom-container', + renderComponent: Page, + addProvider: ({ children }) => { + return {children}; + } + }, +}; + +runApp(appConfig); diff --git a/examples/basic-mpa-webpack/src/pages/Profile/index.tsx b/examples/basic-mpa-webpack/src/pages/Profile/index.tsx new file mode 100644 index 0000000000..8c602c179b --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Profile/index.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import store from './store'; + +const Profile = () => { + const [pageState, pageActions] = store.useModel('counter'); + return ( + <> +

Profile Page...

+
+ + {pageState.count} + +
+ + ); +}; + +export default Profile; diff --git a/examples/basic-mpa-webpack/src/pages/Profile/models/counter.ts b/examples/basic-mpa-webpack/src/pages/Profile/models/counter.ts new file mode 100644 index 0000000000..4f1567bc2d --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Profile/models/counter.ts @@ -0,0 +1,14 @@ +export default { + state: { + count: 0 + }, + + reducers: { + increment (prevState) { + return { count: prevState.count + 1 }; + }, + decrement (prevState) { + return { count: prevState.count - 1 }; + } + } +}; diff --git a/examples/basic-mpa-webpack/src/pages/Profile/store.ts b/examples/basic-mpa-webpack/src/pages/Profile/store.ts new file mode 100644 index 0000000000..fd399d4f11 --- /dev/null +++ b/examples/basic-mpa-webpack/src/pages/Profile/store.ts @@ -0,0 +1,6 @@ +import { createStore } from 'ice'; +import counter from './models/counter'; + +const store = createStore({ counter }); + +export default store; diff --git a/examples/basic-mpa-webpack/tsconfig.json b/examples/basic-mpa-webpack/tsconfig.json new file mode 100644 index 0000000000..a3e86b8d35 --- /dev/null +++ b/examples/basic-mpa-webpack/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "react", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "allowJs": true, + "rootDir": "./", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true, + "types": ["node"], + "paths": { + "@/*": [ + "./src/*" + ], + "ice": [ + ".ice/index.ts" + ], + "ice/*": [ + ".ice/pages/*" + ] + } + } +} diff --git a/examples/basic-mpa-with-swc/package.json b/examples/basic-mpa-with-swc/package.json index 48d5916f75..fb624d7c32 100644 --- a/examples/basic-mpa-with-swc/package.json +++ b/examples/basic-mpa-with-swc/package.json @@ -1,5 +1,5 @@ { - "name": "exmaple-basic-mpa", + "name": "basic-mpa-swc", "description": "", "dependencies": { "react": "^16.4.1", diff --git a/packages/plugin-store/src/babelPluginReplacePath.ts b/packages/plugin-store/src/babelPluginReplacePath.ts index 14e06ee36a..6f0b995140 100644 --- a/packages/plugin-store/src/babelPluginReplacePath.ts +++ b/packages/plugin-store/src/babelPluginReplacePath.ts @@ -86,59 +86,76 @@ interface IGetConfigRoutePathParams { // case1: { "@": "./src", "@pages": "./src/pages" } // case2: { "@src": "./src", "@pages": "./src/pages" } // case3: { "@": "./src", "@/pages": "./src/pages" } -function matchAliasPath(alias: IAlias, value: string, applyMethod: Function): string { - let aliasPath = ''; - // use custom alias +function matchAliasPath( + { + alias, + value, + applyMethod, + rootDir, + }: { + alias: IAlias; + value: string; + applyMethod: Function; + rootDir: string; + }): string { + let srcRelativePath = ''; + Object.keys(alias).forEach(currKey => { if (value.startsWith(currKey)) { const [, ...args] = value.split(currKey); - const currAliasPath = applyMethod('formatPath', path.join(alias[currKey], ...args)); - if (currAliasPath.includes('src/pages')) { - aliasPath = currAliasPath; + const absolutePagePath = applyMethod('formatPath', path.join(alias[currKey], ...args)); + if (absolutePagePath.includes('src/pages')) { + srcRelativePath = path.relative(rootDir, absolutePagePath); } } }); - return aliasPath; + + return srcRelativePath; } /** * 匹配配置式路由下使用的相对路径并返回相对的 src 的相对路径 */ -function matchRelativePath(routesPath: string, value: string, applyMethod: Function): string { - let relativePath = ''; +function matchRelativePath( + { routesPath, + value, + applyMethod, + rootDir, + }: { + routesPath: string; + value: string; + applyMethod: Function; + rootDir: string; + }): string { + let srcRelativePath = ''; if (/^(\.\/|\.{2}\/)/.test(value)) { - relativePath = applyMethod('formatPath', - path.relative(process.cwd(), path.join(routesPath, '..', value)) + srcRelativePath = applyMethod( + 'formatPath', + path.relative(rootDir, path.join(routesPath, '..', value)) ); } - return relativePath; + return srcRelativePath; } /** * 格式化路由的替换路径值 */ function formatPagePath({ routesPath, value, alias, tempDir, applyMethod, rootDir }: IGetConfigRoutePathParams): string { - const matchedPagePath = matchRelativePath(routesPath, value, applyMethod) || matchAliasPath(alias, value, applyMethod); + const matchedPagePath = matchRelativePath({routesPath, value, applyMethod, rootDir}) || matchAliasPath({alias, value, applyMethod, rootDir}); if (matchedPagePath && pagePathRegExp.test(matchedPagePath)) { let newValue = ''; // Note:过滤掉 pages 目录下的单文件形式 if (/src\/pages\/\w+(.tsx|.jsx?)$/.test(value)) { return newValue; } else { - // matchedPagePath 值示例: - // relativePath: ./pages/Home - // alias: /example/src/pages/Home - const pagePathParts = matchedPagePath.split('/'); - const pageName = pagePathParts[pagePathParts.length - 1]; + // matchedPagePath 示例值: src/pages/Home + const [, , pageName] = matchedPagePath.split('/'); newValue = pageName ? path.join(rootDir, tempDir, 'pages', pageName, 'index.tsx') : ''; } return newValue; } else if (matchedPagePath && layoutPathRegExp.test(matchedPagePath)) { - // matchedPagePath 值示例: - // relativePath: ./pages/Home/Layout - // alias: /example/src/pages/Home/Layout - const pagePathParts = matchedPagePath.split('/'); - const pageName = pagePathParts[pagePathParts.length - 2]; + // matchedPagePath 示例值:src/pages/Home/Layout + const [, , pageName] = matchedPagePath.split('/'); const newValue = pageName ? path.join(rootDir, tempDir, 'pages', pageName, 'Layout') : ''; return newValue; } diff --git a/test/basic-mpa.test.ts b/test/basic-mpa-vite.test.ts similarity index 96% rename from test/basic-mpa.test.ts rename to test/basic-mpa-vite.test.ts index a9aa72d5ea..d29c205451 100644 --- a/test/basic-mpa.test.ts +++ b/test/basic-mpa-vite.test.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import { buildFixture, setupBrowser } from './utils/build'; import { IPage } from './utils/browser'; -const example = 'basic-mpa'; +const example = 'basic-mpa-vite'; let page: IPage = null; let browser = null; diff --git a/test/basic-mpa-webpack.test.ts b/test/basic-mpa-webpack.test.ts new file mode 100644 index 0000000000..797d2f86e2 --- /dev/null +++ b/test/basic-mpa-webpack.test.ts @@ -0,0 +1,34 @@ +import * as path from 'path'; +import { buildFixture, setupBrowser } from './utils/build'; +import { IPage } from './utils/browser'; + +const example = 'basic-mpa-webpack'; +let page: IPage = null; +let browser = null; + +buildFixture(example); + +test('open /dashboard', async () => { + const res = await setupBrowser({ example, defaultHtml: 'dashboard.html' }); + page = res.page; + browser = res.browser; + expect(await page.$$text('h2')).toStrictEqual(['Dashboard Page...']); + await browser.close(); +}); + +test('open /detail', async () => { + const res = await setupBrowser({ example, defaultHtml: 'detail.html' }); + page = res.page; + browser = res.browser; + expect(await page.$$text('h2')).toStrictEqual(['Detail Page']); + await browser.close(); +}); + +test('open /home', async () => { + const res = await setupBrowser({ example, defaultHtml: 'home.html' }); + page = res.page; + browser = res.browser; + expect(await page.$$text('h2')).toStrictEqual(['Home Page']); + await browser.close(); +}); + From 8bedeef94b94f4ab557989d257ca367117f204cf Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:03:14 +0800 Subject: [PATCH 17/20] fix: headers is undefined (#5146) --- packages/plugin-i18n/src/utils/getLocaleData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-i18n/src/utils/getLocaleData.ts b/packages/plugin-i18n/src/utils/getLocaleData.ts index d53b94903e..2ea68a7b0f 100644 --- a/packages/plugin-i18n/src/utils/getLocaleData.ts +++ b/packages/plugin-i18n/src/utils/getLocaleData.ts @@ -35,7 +35,7 @@ function getDetectedLocale( { pathname, i18nConfig, - headers, + headers = {}, basename, }: { pathname: string, From a3c3accf51bf2853ed6bb5988252414b96238221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8B=92=E7=8B=92=E7=A5=9E?= Date: Mon, 17 Jan 2022 13:37:34 +0800 Subject: [PATCH 18/20] fix: __pageConfig type error (#5145) * fix: __pageConfig type error * fix: __pageConfig type --- packages/create-app-shared/CHANGELOG.md | 4 ++++ packages/create-app-shared/package.json | 4 ++-- packages/create-app-shared/src/types.ts | 9 ++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/create-app-shared/CHANGELOG.md b/packages/create-app-shared/CHANGELOG.md index 3587f186ab..2ca7d5989d 100644 --- a/packages/create-app-shared/CHANGELOG.md +++ b/packages/create-app-shared/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.3 + +- [fix] `__pageConfig` types for rax MPA + ## 1.2.2 - [fix] miniapp withRouter diff --git a/packages/create-app-shared/package.json b/packages/create-app-shared/package.json index 49993fdaf3..45f1f26d52 100644 --- a/packages/create-app-shared/package.json +++ b/packages/create-app-shared/package.json @@ -1,6 +1,6 @@ { "name": "create-app-shared", - "version": "1.2.2", + "version": "1.2.3", "description": "", "author": "ice-admin@alibaba-inc.com", "homepage": "https://github.com/alibaba/ice#readme", @@ -50,4 +50,4 @@ "@types/rax": "^1.0.6", "react": "^17.0.2" } -} \ No newline at end of file +} diff --git a/packages/create-app-shared/src/types.ts b/packages/create-app-shared/src/types.ts index 76b3e73bf9..2287caf60c 100644 --- a/packages/create-app-shared/src/types.ts +++ b/packages/create-app-shared/src/types.ts @@ -5,7 +5,14 @@ type VoidFunction = () => void; type App = Partial<{ rootId: string, - renderComponent?: ComponentType, + renderComponent?: ComponentType & { + __pageConfig?: { + source?: string; + path?: string; + name?: string; + [key:string]: any; + }; + }, } & Record<'onShow' | 'onHide' | 'onPageNotFound' | 'onShareAppMessage' | 'onUnhandledRejection' | 'onLaunch' | 'onError' | 'onTabItemClick', VoidFunction>>; export interface AppConfig { From 0aed0206b4230e4e3f9eb7b1b5b9dee9fd81fe89 Mon Sep 17 00:00:00 2001 From: Hengchang Lu <44047106+luhc228@users.noreply.github.com> Date: Mon, 17 Jan 2022 13:37:49 +0800 Subject: [PATCH 19/20] Fix: store alias error in swc (#5147) * fix: store alias error in swc * feat: originSourcePath is equal to aliasKey --- packages/plugin-store/src/replacePathLoader.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/plugin-store/src/replacePathLoader.ts b/packages/plugin-store/src/replacePathLoader.ts index 9bc0403078..3c339990b0 100644 --- a/packages/plugin-store/src/replacePathLoader.ts +++ b/packages/plugin-store/src/replacePathLoader.ts @@ -48,9 +48,13 @@ async function loader(content: string, sourceMap: SourceMap) { let absoluteSourcePath: string; if (matchAliasKey) { // handle alias path - const matchAliasPath: string = alias[matchAliasKey]; // .src/* - const replaceWithAliasPath = originSourcePath.replace(RegExp(matchAliasKey), matchAliasPath.replace(/\*/g, '')); - absoluteSourcePath = path.join(rootDir, replaceWithAliasPath); + const matchAliasPath: string = alias[matchAliasKey]; + if (originSourcePath === matchAliasKey) { + absoluteSourcePath = matchAliasPath; + } else if (originSourcePath.startsWith(addLastSlash(matchAliasKey))) { + // e.g.: @/pages/Home -> /users/src/pages/Home + absoluteSourcePath = originSourcePath.replace(RegExp(`^${matchAliasKey}`), matchAliasPath); + } } else { // handle relative path const currentRoutesDir = path.dirname(currentRoutesPath); @@ -82,3 +86,7 @@ function generateRedirectPath({ tempDir, pageName, rootDir }) { const pagePath = path.join(rootDir, tempDir, 'pages', pageName, 'index.tsx'); return formatPath(pagePath); } + +function addLastSlash(filePath: string) { + return filePath.endsWith('/') ? filePath : `${filePath}/`; +} From 4f833cfe86f7f9837c6abef716786efd758ee3e9 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Mon, 17 Jan 2022 13:57:08 +0800 Subject: [PATCH 20/20] chore: version and changelog (#5133) * chore: changelog * fix: dts generation * chore: update lock * chore: version --- examples/dts-generation/build.json | 4 +++- packages/build-app-helpers/package.json | 2 +- packages/build-app-templates/CHANGELOG.md | 4 ++++ packages/build-app-templates/package.json | 6 +++--- packages/build-mpa-config/package.json | 2 +- packages/build-user-config/package.json | 4 ++-- packages/create-app-shared/CHANGELOG.md | 1 + packages/icejs/package.json | 2 +- packages/plugin-app-core/CHANGELOG.md | 4 ++++ packages/plugin-app-core/package.json | 6 +++--- packages/plugin-ice-ssr/CHANGELOG.md | 1 + packages/plugin-ice-ssr/package.json | 2 +- packages/plugin-react-app/package.json | 2 +- packages/plugin-store/package.json | 2 +- packages/runtime/CHANGELOG.md | 4 ++++ packages/runtime/package.json | 4 ++-- packages/vite-plugin-index-html/package.json | 2 +- yarn.lock | 13 +++---------- 18 files changed, 37 insertions(+), 28 deletions(-) diff --git a/examples/dts-generation/build.json b/examples/dts-generation/build.json index 9e26dfeeb6..3e3ed1bdb5 100644 --- a/examples/dts-generation/build.json +++ b/examples/dts-generation/build.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "optimizeRuntime": false +} \ No newline at end of file diff --git a/packages/build-app-helpers/package.json b/packages/build-app-helpers/package.json index 898f010cdc..0e44c3875f 100644 --- a/packages/build-app-helpers/package.json +++ b/packages/build-app-helpers/package.json @@ -30,4 +30,4 @@ "es-module-lexer": "^0.9.0", "fast-glob": "^3.2.7" } -} +} \ No newline at end of file diff --git a/packages/build-app-templates/CHANGELOG.md b/packages/build-app-templates/CHANGELOG.md index 7e1b03f5e3..82de314197 100644 --- a/packages/build-app-templates/CHANGELOG.md +++ b/packages/build-app-templates/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.3 + +- [fix] relative path of `loadStaticModules` + ## 1.1.2 - [fix] rax routerAPI template diff --git a/packages/build-app-templates/package.json b/packages/build-app-templates/package.json index 7e1f4241d4..d1825b7db1 100644 --- a/packages/build-app-templates/package.json +++ b/packages/build-app-templates/package.json @@ -1,6 +1,6 @@ { "name": "@builder/app-templates", - "version": "1.1.2", + "version": "1.1.3", "description": "App templates for ice.js and rax-app", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -10,7 +10,7 @@ "lib": "lib" }, "dependencies": { - "create-app-shared": "^1.2.1" + "create-app-shared": "^1.2.3" }, "files": [ "lib", @@ -20,4 +20,4 @@ "type": "http", "url": "https://github.com/alibaba/ice/tree/master/packages/build-app-templates" } -} +} \ No newline at end of file diff --git a/packages/build-mpa-config/package.json b/packages/build-mpa-config/package.json index e01faba26c..5823f92ade 100644 --- a/packages/build-mpa-config/package.json +++ b/packages/build-mpa-config/package.json @@ -25,7 +25,7 @@ "fs-extra": "^8.1.0", "loader-utils": "^2.0.0", "globby": "^11.0.2", - "@builder/app-templates": "^1.1.0" + "@builder/app-templates": "^1.1.3" }, "devDependencies": { "build-scripts": "^1.1.0" diff --git a/packages/build-user-config/package.json b/packages/build-user-config/package.json index 67485c988e..bf6e20e4b7 100644 --- a/packages/build-user-config/package.json +++ b/packages/build-user-config/package.json @@ -8,7 +8,7 @@ "dependencies": { "@builder/pack": "^0.5.0", "@babel/helper-module-imports": "^7.13.12", - "@builder/app-helpers": "^2.4.2", + "@builder/app-helpers": "^2.5.0", "core-js": "^3.3.1", "deepmerge": "^4.2.2", "eslint-webpack-plugin": "^3.0.1", @@ -45,4 +45,4 @@ "bugs": { "url": "https://github.com/alibaba/ice/issues" } -} +} \ No newline at end of file diff --git a/packages/create-app-shared/CHANGELOG.md b/packages/create-app-shared/CHANGELOG.md index 2ca7d5989d..8c83a6337d 100644 --- a/packages/create-app-shared/CHANGELOG.md +++ b/packages/create-app-shared/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.2.3 +- [fix] revert AppProvider - [fix] `__pageConfig` types for rax MPA ## 1.2.2 diff --git a/packages/icejs/package.json b/packages/icejs/package.json index e29beeec65..f6b3743861 100644 --- a/packages/icejs/package.json +++ b/packages/icejs/package.json @@ -24,7 +24,7 @@ "dependencies": { "@builder/pack": "^0.5.0", "build-scripts": "^1.1.0", - "build-plugin-app-core": "2.1.1", + "build-plugin-app-core": "2.1.2", "build-plugin-helmet": "1.0.2", "build-plugin-ice-auth": "2.0.1", "build-plugin-ice-config": "2.0.2", diff --git a/packages/plugin-app-core/CHANGELOG.md b/packages/plugin-app-core/CHANGELOG.md index 36c7cdada1..7d0f2b576f 100644 --- a/packages/plugin-app-core/CHANGELOG.md +++ b/packages/plugin-app-core/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.1.2 + +- [fix] add buildConfig of i18n + ## 2.1.1 - [fix] circular dependence of lazy when run test diff --git a/packages/plugin-app-core/package.json b/packages/plugin-app-core/package.json index eb51e42709..478342c09f 100644 --- a/packages/plugin-app-core/package.json +++ b/packages/plugin-app-core/package.json @@ -1,6 +1,6 @@ { "name": "build-plugin-app-core", - "version": "2.1.1", + "version": "2.1.2", "description": "the core plugin for icejs and raxjs.", "author": "ice-admin@alibaba-inc.com", "homepage": "", @@ -19,8 +19,8 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "@builder/app-helpers": "^2.4.1", - "@builder/app-templates": "^1.1.0", + "@builder/app-helpers": "^2.5.0", + "@builder/app-templates": "^1.1.3", "chokidar": "^3.4.1", "ejs": "^3.0.1", "fs-extra": "^8.1.0", diff --git a/packages/plugin-ice-ssr/CHANGELOG.md b/packages/plugin-ice-ssr/CHANGELOG.md index dece0c9091..0ecf3f229c 100644 --- a/packages/plugin-ice-ssr/CHANGELOG.md +++ b/packages/plugin-ice-ssr/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.1.0 - [feat] support SSR in mode Vite +- [feat] support i18n generation ## 3.0.6 diff --git a/packages/plugin-ice-ssr/package.json b/packages/plugin-ice-ssr/package.json index af408669ab..d3b5a6350a 100644 --- a/packages/plugin-ice-ssr/package.json +++ b/packages/plugin-ice-ssr/package.json @@ -23,7 +23,7 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "@builder/app-helpers": "^2.4.1", + "@builder/app-helpers": "^2.5.0", "@builder/webpack-config": "^2.0.0", "@loadable/babel-plugin": "^5.13.2", "@loadable/webpack-plugin": "^5.14.0", diff --git a/packages/plugin-react-app/package.json b/packages/plugin-react-app/package.json index 810cdac506..e2e1793871 100644 --- a/packages/plugin-react-app/package.json +++ b/packages/plugin-react-app/package.json @@ -16,7 +16,7 @@ "@builder/app-helpers": "^2.5.0", "@builder/jest-config": "^2.0.0", "@builder/pack": "^0.5.0", - "@builder/user-config": "^2.0.3", + "@builder/user-config": "^2.1.0", "@builder/webpack-config": "^2.0.0", "chalk": "^4.0.0", "debug": "^4.1.1", diff --git a/packages/plugin-store/package.json b/packages/plugin-store/package.json index 7b8cc41ed6..323567548d 100644 --- a/packages/plugin-store/package.json +++ b/packages/plugin-store/package.json @@ -24,7 +24,7 @@ "test": "echo \"Error: run tests from root\" && exit 1" }, "dependencies": { - "@builder/app-helpers": "^2.4.1", + "@builder/app-helpers": "^2.5.0", "@ice/store": "^2.0.0-3", "chalk": "^4.1.0", "enhanced-resolve": "^4.3.0", diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 2445cc45c8..6f3de74ec7 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.1 + +- [feat] add path-to-regexp@1.x dependency + ## 0.1.0 - [feat] dependency of axios and utils for axios \ No newline at end of file diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 003a8443b8..dcb4061256 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@ice/runtime", - "version": "0.1.2", + "version": "0.1.1", "description": "runtime dependencies for plugin runtime", "main": "lib/index.js", "scripts": { @@ -17,4 +17,4 @@ "path-to-regexp": "^1.8.0" }, "sideEffects": false -} +} \ No newline at end of file diff --git a/packages/vite-plugin-index-html/package.json b/packages/vite-plugin-index-html/package.json index 4e2e260c66..b47d9a6fa3 100644 --- a/packages/vite-plugin-index-html/package.json +++ b/packages/vite-plugin-index-html/package.json @@ -25,4 +25,4 @@ "fs-extra": "^10.0.0", "html-minifier-terser": "^6.0.2" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 592a12a82b..78775de2af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1120,7 +1120,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== @@ -5785,12 +5785,7 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== - -cookie@^0.4.0: +cookie@0.4.1, cookie@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== @@ -11383,11 +11378,10 @@ miniapp-builder-shared@^0.2.2: execa "^5.0.0" fs-extra "^8.0.1" -miniapp-history@^0.1.0: +miniapp-history@^0.1.6: version "0.1.7" resolved "https://registry.yarnpkg.com/miniapp-history/-/miniapp-history-0.1.7.tgz#f5c04ce5aacc9a0344f25a64342f3863cf65dfa8" integrity sha512-q/+f8ncjeyDvPahMLEeknvJiKcVwZLVNDm3tNeB4o8sxJxoQbHIaStJ9SpQkbdhJn971kmoUQyH8aH26O7OvIw== - dependencies: universal-env "^3.0.0" @@ -16682,7 +16676,6 @@ vite@^2.0.0, vite@^2.3.4, vite@^2.4.2, vite@^2.4.3, vite@^2.5.0, vite@^2.5.3, vi version "2.7.10" resolved "https://registry.yarnpkg.com/vite/-/vite-2.7.10.tgz#d12c4c10e56a0ecf7890cb529c15996c6111218f" integrity sha512-KEY96ntXUid1/xJihJbgmLZx7QSC2D4Tui0FdS0Old5OokYzFclcofhtxtjDdGOk/fFpPbHv9yw88+rB93Tb8w== - dependencies: esbuild "^0.13.12" postcss "^8.4.5"