@umijs/preset-umi 4.0.0-beta.12 → 4.0.0-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/commands/build.js +1 -1
  2. package/dist/commands/dev/createRouteMiddleware.js +7 -3
  3. package/dist/commands/dev/dev.js +6 -1
  4. package/dist/commands/dev/getMarkupArgs.d.ts +2 -0
  5. package/dist/commands/dev/getMarkupArgs.js +3 -0
  6. package/dist/features/appData/appData.js +33 -0
  7. package/dist/features/check/check.js +11 -0
  8. package/dist/features/configPlugins/configPlugins.js +7 -3
  9. package/dist/features/configPlugins/schema.js +12 -4
  10. package/dist/features/esmi/Service.d.ts +19 -0
  11. package/dist/features/esmi/Service.js +72 -3
  12. package/dist/features/esmi/esbuildPlugins/requireToImport.d.ts +8 -0
  13. package/dist/features/esmi/esbuildPlugins/requireToImport.js +61 -0
  14. package/dist/features/esmi/esbuildPlugins/topLevelExternal.d.ts +13 -0
  15. package/dist/features/esmi/esbuildPlugins/topLevelExternal.js +37 -0
  16. package/dist/features/esmi/esmi.js +81 -12
  17. package/dist/features/favicon/favicon.js +3 -1
  18. package/dist/features/lowImport/babelPlugin.d.ts +2 -0
  19. package/dist/features/lowImport/babelPlugin.js +28 -6
  20. package/dist/features/lowImport/lowImport.js +8 -3
  21. package/dist/features/mock/getMockData.d.ts +4 -0
  22. package/dist/features/mock/getMockData.js +10 -5
  23. package/dist/features/mock/mock.js +9 -2
  24. package/dist/features/polyfill/polyfill.js +38 -8
  25. package/dist/features/polyfill/publicPathPolyfill.d.ts +3 -0
  26. package/dist/features/polyfill/publicPathPolyfill.js +14 -0
  27. package/dist/features/tmpFiles/routes.js +14 -2
  28. package/dist/features/tmpFiles/tmpFiles.js +93 -8
  29. package/dist/index.js +2 -0
  30. package/dist/libs/scan.d.ts +5 -1
  31. package/dist/libs/scan.js +41 -1
  32. package/dist/registerMethods.js +6 -1
  33. package/dist/types.d.ts +2 -1
  34. package/dist/utils/transformIEAR.d.ts +22 -0
  35. package/dist/utils/transformIEAR.js +100 -0
  36. package/package.json +16 -15
  37. package/templates/history.tpl +15 -0
  38. package/templates/plugin.tpl +14 -0
  39. package/templates/umi.tpl +32 -7
  40. package/dist/libs/moduleGraph.d.ts +0 -2
  41. package/dist/libs/moduleGraph.js +0 -6
@@ -110,7 +110,7 @@ umi build --clean
110
110
  const { vite } = api.args;
111
111
  const markupArgs = yield (0, getMarkupArgs_1.getMarkupArgs)({ api });
112
112
  // @ts-ignore
113
- const markup = yield (0, server_1.getMarkup)(Object.assign(Object.assign({}, markupArgs), { scripts: ['/umi.js'].concat(markupArgs.scripts), esmScript: !!opts.config.esm || vite, path: '/' }));
113
+ const markup = yield (0, server_1.getMarkup)(Object.assign(Object.assign({}, markupArgs), { styles: ['/umi.css'].concat(markupArgs.styles), scripts: ['/umi.js'].concat(markupArgs.scripts), esmScript: !!opts.config.esm || vite, path: '/' }));
114
114
  (0, fs_1.writeFileSync)((0, path_1.join)(api.paths.absOutputPath, 'index.html'), markup, 'utf-8');
115
115
  utils_1.logger.event('build index.html');
116
116
  // print size
@@ -24,11 +24,15 @@ window.__vite_plugin_react_preamble_installed__ = true
24
24
  function createRouteMiddleware(opts) {
25
25
  return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
26
26
  const { vite } = opts.api.args;
27
+ const viteScripts = [
28
+ // add noshim attr for skip importmap shim logic for this modules
29
+ { content: viteRefreshScript, noshim: '' },
30
+ { src: '/@vite/client', noshim: '' },
31
+ opts.api.appData.hasSrcDir ? '/src/.umi/umi.ts' : '/.umi/umi.ts',
32
+ ];
27
33
  const markupArgs = yield (0, getMarkupArgs_1.getMarkupArgs)(opts);
28
34
  // @ts-ignore
29
- const requestHandler = yield (0, server_1.createRequestHandler)(Object.assign(Object.assign({}, markupArgs), { scripts: (vite
30
- ? [viteRefreshScript, '/@vite/client', '/.umi/umi.ts']
31
- : ['/umi.js']).concat(markupArgs.scripts), esmScript: vite }));
35
+ const requestHandler = yield (0, server_1.createRequestHandler)(Object.assign(Object.assign({}, markupArgs), { styles: ['/umi.css'].concat(markupArgs.styles), scripts: (vite ? viteScripts : ['/umi.js']).concat(markupArgs.scripts), esmScript: vite }));
32
36
  requestHandler(req, res, next);
33
37
  });
34
38
  }
@@ -238,7 +238,12 @@ PORT=8888 umi dev
238
238
  key: 'onDevCompileDone',
239
239
  args: opts,
240
240
  });
241
- }, mfsuWithESBuild: (_a = api.config.mfsu) === null || _a === void 0 ? void 0 : _a.esbuild });
241
+ }, mfsuWithESBuild: (_a = api.config.mfsu) === null || _a === void 0 ? void 0 : _a.esbuild, cache: {
242
+ buildDependencies: [
243
+ api.pkgPath,
244
+ api.service.configManager.mainConfigFile || '',
245
+ ].filter(Boolean),
246
+ } });
242
247
  if (enableVite) {
243
248
  yield bundlerVite.dev(opts);
244
249
  }
@@ -2,6 +2,8 @@ import { IApi } from '../../types';
2
2
  export declare function getMarkupArgs(opts: {
3
3
  api: IApi;
4
4
  }): Promise<{
5
+ mountElementId: any;
6
+ base: any;
5
7
  routes: any;
6
8
  favicon: any;
7
9
  headScripts: any;
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.getMarkupArgs = void 0;
16
16
  const cheerio_1 = __importDefault(require("@umijs/utils/compiled/cheerio"));
17
17
  function getMarkupArgs(opts) {
18
+ var _a;
18
19
  return __awaiter(this, void 0, void 0, function* () {
19
20
  const headScripts = yield opts.api.applyPlugins({
20
21
  key: 'addHTMLHeadScripts',
@@ -41,6 +42,8 @@ function getMarkupArgs(opts) {
41
42
  initialValue: opts.api.config.favicon,
42
43
  });
43
44
  return {
45
+ mountElementId: opts.api.config.mountElementId,
46
+ base: ((_a = opts.api.config.history) === null || _a === void 0 ? void 0 : _a.type) === 'browser' ? opts.api.config.base : '/',
44
47
  routes: opts.api.appData.routes,
45
48
  favicon,
46
49
  headScripts,
@@ -9,8 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const bundler_utils_1 = require("@umijs/bundler-utils");
12
13
  const utils_1 = require("@umijs/utils");
14
+ const fs_1 = require("fs");
13
15
  const path_1 = require("path");
16
+ const watch_1 = require("../../commands/dev/watch");
14
17
  const routes_1 = require("../tmpFiles/routes");
15
18
  exports.default = (api) => {
16
19
  api.modifyAppData((memo) => __awaiter(void 0, void 0, void 0, function* () {
@@ -25,6 +28,36 @@ exports.default = (api) => {
25
28
  memo.react = {
26
29
  version: require((0, path_1.join)(api.config.alias.react, 'package.json')).version,
27
30
  };
31
+ memo.appJS = yield getAppJsInfo();
28
32
  return memo;
29
33
  }));
34
+ // Execute earliest, so that other onGenerateFiles can get it
35
+ api.register({
36
+ key: 'onGenerateFiles',
37
+ fn(args) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ if (!args.isFirstTime) {
40
+ api.appData.appJS = yield getAppJsInfo();
41
+ }
42
+ });
43
+ },
44
+ stage: Number.NEGATIVE_INFINITY,
45
+ });
46
+ function getAppJsInfo() {
47
+ return __awaiter(this, void 0, void 0, function* () {
48
+ for (const path of (0, watch_1.expandJSPaths)((0, path_1.join)(api.paths.absSrcPath, 'app'))) {
49
+ if ((0, fs_1.existsSync)(path)) {
50
+ const [_, exports] = yield (0, bundler_utils_1.parseModule)({
51
+ path,
52
+ content: (0, fs_1.readFileSync)(path, 'utf-8'),
53
+ });
54
+ return {
55
+ path,
56
+ exports,
57
+ };
58
+ }
59
+ }
60
+ return null;
61
+ });
62
+ }
30
63
  };
@@ -31,4 +31,15 @@ exports.default = (api) => {
31
31
  },
32
32
  });
33
33
  }));
34
+ api.onCheckCode((args) => {
35
+ // Fixed version import is not allowed
36
+ // e.g. import { X } from '_@ant-design_icons@4.7.0@ant-design/icons'
37
+ if (['cnpm', 'tnpm'].includes(api.appData.npmClient)) {
38
+ args.imports.forEach(({ source }) => {
39
+ if (/@\d/.test(source)) {
40
+ throw new Error(`${source} is not allowed to import.`);
41
+ }
42
+ });
43
+ }
44
+ });
34
45
  };
@@ -16,8 +16,7 @@ function resolveProjectDep(opts) {
16
16
  exports.default = (api) => {
17
17
  const configDefaults = {
18
18
  alias: {
19
- umi: process.env.UMI_DIR,
20
- '@umijs/renderer-react': (0, path_1.dirname)(require.resolve('@umijs/renderer-react/package.json')),
19
+ umi: '@@/exports',
21
20
  react: resolveProjectDep({
22
21
  pkg: api.pkg,
23
22
  cwd: api.cwd,
@@ -34,6 +33,9 @@ exports.default = (api) => {
34
33
  externals: {},
35
34
  autoCSSModules: true,
36
35
  publicPath: '/',
36
+ mountElementId: 'root',
37
+ base: '/',
38
+ history: { type: 'browser' },
37
39
  };
38
40
  const bundleSchemas = (0, schema_1.getSchemas)();
39
41
  const extraSchemas = (0, schema_2.getSchemas)();
@@ -55,7 +57,9 @@ exports.default = (api) => {
55
57
  }
56
58
  // api.paths is ready after register
57
59
  api.modifyConfig((memo, args) => {
58
- memo.alias = Object.assign(Object.assign({}, memo.alias), { '@': args.paths.absSrcPath, '@@': args.paths.absTmpPath });
60
+ memo.alias = Object.assign(Object.assign({}, memo.alias), { '@': args.paths.absSrcPath, '@@': args.paths.absTmpPath,
61
+ // like vite, use to pre-bundling dependencies in vite mode
62
+ '@fs': '/' });
59
63
  return memo;
60
64
  });
61
65
  };
@@ -4,13 +4,21 @@ exports.getSchemas = void 0;
4
4
  const utils_1 = require("@umijs/utils");
5
5
  function getSchemas() {
6
6
  return {
7
- plugins: (Joi) => Joi.array().items(Joi.string()),
8
- publicPath: (Joi) => Joi.string().regex(/\/$/).error(new Error('publicPath must end with /')),
7
+ base: (Joi) => Joi.string(),
9
8
  favicon: (Joi) => Joi.string(),
10
- headScripts: (Joi) => Joi.array().items(Joi.alternatives(Joi.string())),
11
- scripts: (Joi) => Joi.array().items(Joi.alternatives(Joi.string())),
9
+ headScripts: (Joi) => Joi.array(),
10
+ history: (Joi) => Joi.object({
11
+ type: Joi.string().valid('browser', 'hash', 'memory'),
12
+ }),
13
+ links: (Joi) => Joi.array(),
14
+ metas: (Joi) => Joi.array(),
15
+ mountElementId: (Joi) => Joi.string(),
12
16
  npmClient: (Joi) => Joi.string().valid(utils_1.NpmClientEnum.pnpm, utils_1.NpmClientEnum.tnpm, utils_1.NpmClientEnum.cnpm, utils_1.NpmClientEnum.yarn, utils_1.NpmClientEnum.npm),
17
+ plugins: (Joi) => Joi.array().items(Joi.string()),
18
+ publicPath: (Joi) => Joi.string().regex(/\/$/).error(new Error('publicPath must end with /')),
13
19
  routes: (Joi) => Joi.array().items(Joi.object()),
20
+ scripts: (Joi) => Joi.array(),
21
+ styles: (Joi) => Joi.array(),
14
22
  };
15
23
  }
16
24
  exports.getSchemas = getSchemas;
@@ -38,9 +38,28 @@ export interface IPkgData {
38
38
  */
39
39
  export default class ESMIService {
40
40
  cdnOrigin: string;
41
+ cacheDir: string;
42
+ cache: Record<string, IImportmapData>;
41
43
  constructor(opts: {
42
44
  cdnOrigin: string;
45
+ cacheDir: string;
43
46
  });
47
+ /**
48
+ * get cache file path by cache key
49
+ * @param data pkg data
50
+ */
51
+ static getCacheKey(data: IPkgData): string;
52
+ /**
53
+ * get importmap cache by cache key
54
+ * @param key cache key
55
+ */
56
+ getCache(key: string): IImportmapData;
57
+ /**
58
+ * set importmap cache
59
+ * @param key cache key
60
+ * @param data importmap data
61
+ */
62
+ setCache(key: string, data: IImportmapData): void;
44
63
  /**
45
64
  * build importmap from deps tree
46
65
  * @param data package data
@@ -8,15 +8,64 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  const utils_1 = require("@umijs/utils");
16
+ const crypto_1 = require("crypto");
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const path_1 = __importDefault(require("path"));
13
19
  /**
14
20
  * class for connect esmi server
15
21
  */
16
22
  class ESMIService {
17
23
  constructor(opts) {
18
24
  this.cdnOrigin = '';
25
+ this.cacheDir = '';
26
+ this.cache = {};
19
27
  this.cdnOrigin = opts.cdnOrigin;
28
+ this.cacheDir = opts.cacheDir;
29
+ // restore local cache
30
+ const cacheFilePath = path_1.default.join(this.cacheDir, 'importmap.json');
31
+ if (fs_1.default.existsSync(cacheFilePath)) {
32
+ try {
33
+ this.cache = require(cacheFilePath);
34
+ }
35
+ catch (_a) {
36
+ /* nothing */
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * get cache file path by cache key
42
+ * @param data pkg data
43
+ */
44
+ static getCacheKey(data) {
45
+ const hash = (0, crypto_1.createHash)('md4');
46
+ hash.update(JSON.stringify(data.pkgInfo.exports));
47
+ return hash.digest('hex');
48
+ }
49
+ /**
50
+ * get importmap cache by cache key
51
+ * @param key cache key
52
+ */
53
+ getCache(key) {
54
+ return this.cache[key];
55
+ }
56
+ /**
57
+ * set importmap cache
58
+ * @param key cache key
59
+ * @param data importmap data
60
+ */
61
+ setCache(key, data) {
62
+ this.cache[key] = data;
63
+ // create cache dir
64
+ if (!fs_1.default.existsSync(this.cacheDir)) {
65
+ fs_1.default.mkdirSync(this.cacheDir, { recursive: true });
66
+ }
67
+ // write cache to file system
68
+ fs_1.default.writeFileSync(path_1.default.join(this.cacheDir, 'importmap.json'), JSON.stringify(this.cache, null, 2));
20
69
  }
21
70
  /**
22
71
  * build importmap from deps tree
@@ -37,16 +86,36 @@ class ESMIService {
37
86
  */
38
87
  getImportmap(data) {
39
88
  return __awaiter(this, void 0, void 0, function* () {
89
+ const cacheKey = ESMIService.getCacheKey(data);
90
+ const cache = this.getCache(cacheKey);
91
+ const stamp = +new Date();
92
+ // use valid cache first
93
+ if (cache) {
94
+ utils_1.logger.info('ESMi cache used');
95
+ return cache;
96
+ }
97
+ // log dependency list
98
+ utils_1.logger.info(utils_1.chalk.greenBright('Pre-compiling dependencies on esmi:'));
99
+ data.pkgInfo.exports[0].deps.forEach((dep) => {
100
+ console.log(utils_1.chalk.yellow(` ${dep.name}`));
101
+ });
40
102
  // get the build ticket id
41
103
  const ticketId = yield this.build(data);
42
- // continue to the next request after 1s
104
+ utils_1.logger.info(`ticketId: ${ticketId}`);
105
+ // continue to the next request after 2s
43
106
  const next = () => new Promise((resolve) => setTimeout(() => resolve(deferrer()), 2000));
44
107
  const deferrer = () => {
45
108
  return utils_1.axios
46
109
  .get(`${this.cdnOrigin}/api/v1/esm/importmap/${ticketId}`)
47
- .then((res) => (res.data.success ? res.data.data : next()), next);
110
+ .then((res) => {
111
+ if (res.data.success) {
112
+ this.setCache(cacheKey, res.data.data);
113
+ utils_1.logger.info(`Done, took ${((+new Date() - stamp) / 1000).toFixed(1)}s`);
114
+ return res.data.data;
115
+ }
116
+ return next();
117
+ }, next);
48
118
  };
49
- // TODO: timeout + time spend log
50
119
  return deferrer();
51
120
  });
52
121
  }
@@ -0,0 +1,8 @@
1
+ import { Plugin } from '@umijs/bundler-utils/compiled/esbuild';
2
+ import type { DepOptimizationOptions } from 'vite';
3
+ /**
4
+ * transform require call to import
5
+ */
6
+ export default function requireToImportPlugin({ exclude, }: {
7
+ exclude: NonNullable<DepOptimizationOptions['exclude']>;
8
+ }): Plugin;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const lodash_1 = require("@umijs/utils/compiled/lodash");
13
+ /**
14
+ * transform require call to import
15
+ */
16
+ function requireToImportPlugin({ exclude, }) {
17
+ const regSafeExclude = exclude.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
18
+ const requireRegExp = new RegExp(`^(${regSafeExclude.join('|')})$`);
19
+ return {
20
+ name: 'preset-umi:esmi-require-to-import',
21
+ setup(build) {
22
+ // handler require calls for external deps
23
+ build.onResolve({
24
+ filter: requireRegExp,
25
+ }, (args) => __awaiter(this, void 0, void 0, function* () {
26
+ if (args.kind === 'require-call') {
27
+ return {
28
+ path: args.path,
29
+ namespace: 'esmi-require-to-import',
30
+ pluginData: {
31
+ resolveDir: args.resolveDir,
32
+ },
33
+ };
34
+ }
35
+ }));
36
+ // replace load content
37
+ build.onLoad({
38
+ filter: /.*/,
39
+ namespace: 'esmi-require-to-import',
40
+ }, (args) => {
41
+ const { resolveDir } = args.pluginData || {};
42
+ const packageName = args.path;
43
+ const starSpecifier = `${(0, lodash_1.camelCase)(packageName)}Star`;
44
+ const defaultSpecifier = `${(0, lodash_1.camelCase)(packageName)}Default`;
45
+ return {
46
+ resolveDir,
47
+ contents: [
48
+ `import * as ${starSpecifier} from '${packageName}';`,
49
+ '',
50
+ `const ${defaultSpecifier} = ${starSpecifier}.default ? ${starSpecifier}.default : ${starSpecifier};`,
51
+ '',
52
+ `export default ${defaultSpecifier};`,
53
+ `export * from '${packageName}';`,
54
+ '',
55
+ ].join('\n'),
56
+ };
57
+ });
58
+ },
59
+ };
60
+ }
61
+ exports.default = requireToImportPlugin;
@@ -0,0 +1,13 @@
1
+ import { Plugin } from '@umijs/bundler-utils/compiled/esbuild';
2
+ import type { DepOptimizationOptions } from 'vite';
3
+ import type { createResolver } from '../../../libs/scan';
4
+ /**
5
+ * only external top level import, exclude sub-path imports for esmi
6
+ * example:
7
+ * - import from 'antd' will be externalized
8
+ * - import from 'antd/dist/antd.less' will not be externalized
9
+ */
10
+ export default function topLevelExternal({ exclude, resolver, }: {
11
+ exclude: NonNullable<DepOptimizationOptions['exclude']>;
12
+ resolver: ReturnType<typeof createResolver>;
13
+ }): Plugin;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ /**
13
+ * only external top level import, exclude sub-path imports for esmi
14
+ * example:
15
+ * - import from 'antd' will be externalized
16
+ * - import from 'antd/dist/antd.less' will not be externalized
17
+ */
18
+ function topLevelExternal({ exclude, resolver, }) {
19
+ const regSafeExclude = exclude.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
20
+ const subImportRegExp = new RegExp(`^(${regSafeExclude.join('|')})/`);
21
+ const extRegExp = /\.((?<!d)\.ts|jsx?|tsx)$/;
22
+ return {
23
+ name: 'preset-umi:esmi-top-level-external',
24
+ setup(build) {
25
+ build.onResolve({
26
+ filter: subImportRegExp,
27
+ }, (args) => __awaiter(this, void 0, void 0, function* () {
28
+ const resolved = yield resolver.resolve(args.resolveDir, args.path);
29
+ // only process javascript-like files
30
+ if (extRegExp.test(resolved)) {
31
+ return { path: resolved };
32
+ }
33
+ }));
34
+ },
35
+ };
36
+ }
37
+ exports.default = topLevelExternal;
@@ -16,8 +16,11 @@ const es_module_lexer_1 = require("@umijs/bundler-utils/compiled/es-module-lexer
16
16
  const magic_string_1 = __importDefault(require("magic-string"));
17
17
  const path_1 = require("path");
18
18
  const scan_1 = require("../../libs/scan");
19
+ const requireToImport_1 = __importDefault(require("./esbuildPlugins/requireToImport"));
20
+ const topLevelExternal_1 = __importDefault(require("./esbuildPlugins/topLevelExternal"));
19
21
  const Service_1 = __importDefault(require("./Service"));
20
22
  let importmap = { imports: {}, scopes: {} };
23
+ let importmatches = {};
21
24
  /**
22
25
  * esmi vite plugin
23
26
  */
@@ -25,13 +28,36 @@ function esmi(opts) {
25
28
  return {
26
29
  name: 'preset-umi:esmi',
27
30
  configResolved(config) {
31
+ var _a, _b;
32
+ var _c, _d;
28
33
  const { include, exclude } = config.optimizeDeps;
34
+ (_a = (_c = config.optimizeDeps).include) !== null && _a !== void 0 ? _a : (_c.include = []);
29
35
  // do not pre-compile deps which will be loaded by importmap (for top-level deps)
30
36
  if (include === null || include === void 0 ? void 0 : include.length) {
31
- config.optimizeDeps.include = include.filter((item) => !importmap.imports[item]);
37
+ config.optimizeDeps.include = include.filter((item) => !importmatches[item] && !importmap.imports[item]);
32
38
  }
33
- // exclude pre-compile deps which within importmap by default (for nested deps)
34
- config.optimizeDeps.exclude = (exclude || []).concat(Object.keys(importmap.imports).filter((item) => exclude === null || exclude === void 0 ? void 0 : exclude.includes(item)));
39
+ // exclude pre-compile deps
40
+ config.optimizeDeps.exclude = [
41
+ ...new Set([
42
+ // deps from user config
43
+ ...(exclude || []),
44
+ // deps from local scan
45
+ ...Object.keys(importmatches),
46
+ // deps from esmi analyze result
47
+ ...Object.keys(importmap.imports),
48
+ ]),
49
+ ];
50
+ // apply esbuild plugins
51
+ (_b = (_d = config.optimizeDeps).esbuildOptions) !== null && _b !== void 0 ? _b : (_d.esbuildOptions = {});
52
+ config.optimizeDeps.esbuildOptions.plugins = [
53
+ // transform require call to import
54
+ (0, requireToImport_1.default)({ exclude: config.optimizeDeps.exclude }),
55
+ // make sure vite only external top-level npm imports, and resolve sub-path npm imports
56
+ (0, topLevelExternal_1.default)({
57
+ exclude: config.optimizeDeps.exclude,
58
+ resolver: opts.resolver,
59
+ }),
60
+ ].concat(config.optimizeDeps.esbuildOptions.plugins || []);
35
61
  },
36
62
  transform(source) {
37
63
  try {
@@ -42,9 +68,17 @@ function esmi(opts) {
42
68
  imports.forEach((item) => {
43
69
  const { n: specifier, s: start, e: end } = item;
44
70
  // replace npm package to CDN url for matched imports
45
- if (specifier && importmap.imports[specifier]) {
46
- s !== null && s !== void 0 ? s : (s = new magic_string_1.default(source));
47
- s.overwrite(start, end, importmap.imports[specifier]);
71
+ if (specifier) {
72
+ const replacement =
73
+ // search from local scan matches first (for alias)
74
+ (importmatches[specifier] &&
75
+ importmap.imports[importmatches[specifier]]) ||
76
+ // search from esmi analyze result
77
+ importmap.imports[specifier];
78
+ if (replacement) {
79
+ s !== null && s !== void 0 ? s : (s = new magic_string_1.default(source));
80
+ s.overwrite(start, end, replacement);
81
+ }
48
82
  }
49
83
  });
50
84
  return (s === null || s === void 0 ? void 0 : s.toString()) || source;
@@ -78,7 +112,11 @@ function generatePkgData(api) {
78
112
  name: 'default',
79
113
  path: 'es/index.js',
80
114
  from: '',
81
- deps: Object.entries(api.appData.deps).map(([name, version]) => ({
115
+ deps: Object.entries(api.appData.deps)
116
+ // only compile entry imports
117
+ .filter(([_, { matches }]) => matches.length)
118
+ // convert to esmi config
119
+ .map(([name, { version }]) => ({
82
120
  name,
83
121
  version,
84
122
  usedMap: {
@@ -109,13 +147,30 @@ exports.default = (api) => {
109
147
  });
110
148
  // skip umi by default
111
149
  delete api.appData.deps['umi'];
150
+ // FIXME: force include react & react-dom
151
+ api.appData.deps['react'].version = api.appData.react.version;
152
+ api.appData.deps['react-dom'] = {
153
+ version: api.appData.react.version,
154
+ matches: ['react-dom'],
155
+ subpaths: [],
156
+ };
112
157
  const data = generatePkgData(api);
113
158
  const deps = data.pkgInfo.exports.reduce((r, exp) => r.concat(exp.deps.map((dep) => dep.name)), []);
114
159
  const hasNewDep = deps.some((i) => !importmap.imports[i]);
115
160
  // update importmap from esm if there has new import
116
161
  if (hasNewDep) {
117
- // TODO: add local cache and restore
118
162
  importmap = (_a = (yield service.getImportmap(data))) === null || _a === void 0 ? void 0 : _a.importMap;
163
+ // update matches map to dep name
164
+ importmatches = Object.keys(api.appData.deps).reduce((r, dep) => {
165
+ // filter subpath imports
166
+ if (!api.appData.deps[dep].subpaths.length) {
167
+ // map all matches to dep name
168
+ api.appData.deps[dep].matches.forEach((m) => {
169
+ r[m] = dep;
170
+ });
171
+ }
172
+ return r;
173
+ }, {});
119
174
  // because we will replaced package name to CDN url in vite plugin
120
175
  // so we must append scope rules for the CDN url like the import specifier
121
176
  // example:
@@ -136,7 +191,10 @@ exports.default = (api) => {
136
191
  key: 'esmi',
137
192
  config: {
138
193
  schema(Joi) {
139
- return Joi.object();
194
+ return Joi.object({
195
+ cdnOrigin: Joi.string(),
196
+ shimUrl: Joi.string(),
197
+ });
140
198
  },
141
199
  },
142
200
  enableBy: api.EnableBy.config,
@@ -151,7 +209,10 @@ exports.default = (api) => {
151
209
  api.onBeforeCompiler(() => __awaiter(void 0, void 0, void 0, function* () {
152
210
  if (api.args.vite) {
153
211
  // init esmi service
154
- service = new Service_1.default({ cdnOrigin: api.config.esmi.cdnOrigin });
212
+ service = new Service_1.default({
213
+ cdnOrigin: api.config.esmi.cdnOrigin,
214
+ cacheDir: (0, path_1.join)(api.cwd, '.esmi'),
215
+ });
155
216
  // init project resolver
156
217
  resolver = (0, scan_1.createResolver)({
157
218
  alias: api.config.alias,
@@ -162,10 +223,17 @@ exports.default = (api) => {
162
223
  }));
163
224
  // append ipmortmap script for HTML
164
225
  api.modifyHTML(($) => {
165
- const scp = $('<script type="importmap"></script>');
226
+ const scp = $('<script type="importmap"></script>\n');
166
227
  scp.html(JSON.stringify(importmap, null, 2));
167
228
  $('head > script:eq(0)').before(scp);
168
- // TODO: polyfill for legacy browser
229
+ // append importmap shim script
230
+ if (api.config.esmi.shimUrl) {
231
+ $('body > script:eq(0)').before($(`<script src="${api.config.esmi.shimUrl}"></script>\n`));
232
+ }
233
+ // preload for importmap modules
234
+ Object.values(importmap.imports).forEach((url) => {
235
+ scp.before($(`<link rel="modulepreload" href="${url}" />\n`));
236
+ });
169
237
  return $;
170
238
  });
171
239
  if (api.args.vite) {
@@ -178,6 +246,7 @@ exports.default = (api) => {
178
246
  yield refreshImportMap();
179
247
  // TODO: refresh page when importmap changed
180
248
  }),
249
+ resolver,
181
250
  }));
182
251
  return memo;
183
252
  });
@@ -53,6 +53,8 @@ exports.default = (api) => {
53
53
  }
54
54
  });
55
55
  api.modifyHTMLFavicon((memo) => {
56
- return `${api.config.publicPath}${api.appData.faviconFile}` || memo;
56
+ return api.appData.faviconFile
57
+ ? `${api.config.publicPath}${api.appData.faviconFile}`
58
+ : memo;
57
59
  });
58
60
  };
@@ -3,11 +3,13 @@ import * as t from '@umijs/bundler-utils/compiled/babel/types';
3
3
  import { IOpts } from './lowImport';
4
4
  interface IPluginOpts {
5
5
  opts: IOpts;
6
+ css: string;
6
7
  }
7
8
  export default function (): {
8
9
  visitor: {
9
10
  Identifier(path: Babel.NodePath<t.Identifier>, state: {
10
11
  opts: IPluginOpts;
12
+ file: any;
11
13
  }): void;
12
14
  MemberExpression(path: Babel.NodePath<t.MemberExpression>, state: {
13
15
  opts: IPluginOpts;