@modern-js/builder 2.5.0 → 2.6.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @modern-js/builder
2
2
 
3
+ ## 2.6.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 107f674: feat(builder): add dev.beforeStartUrl config
8
+
9
+ feat(builder): 新增 dev.beforeStartUrl 配置项
10
+
11
+ - 0fe658a: feat(builder): support passing URL to html.favicon
12
+
13
+ feat(builder): 支持在 html.favicon 中直接传入 URL
14
+
15
+ - Updated dependencies [b92d6db]
16
+ - Updated dependencies [e1f799e]
17
+ - Updated dependencies [107f674]
18
+ - Updated dependencies [7915ab3]
19
+ - Updated dependencies [03d7f7d]
20
+ - Updated dependencies [fae9d1b]
21
+ - Updated dependencies [0fe658a]
22
+ - Updated dependencies [62930b9]
23
+ - @modern-js/builder-shared@2.6.0
24
+ - @modern-js/utils@2.6.0
25
+
3
26
  ## 2.5.0
4
27
 
5
28
  ### Patch Changes
@@ -0,0 +1,3 @@
1
+ import type { DefaultBuilderPlugin, SharedBuilderPluginAPI } from '@modern-js/builder-shared';
2
+ export declare const applyInjectTags: (api: SharedBuilderPluginAPI) => void;
3
+ export declare const builderPluginHtml: () => DefaultBuilderPlugin;
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.builderPluginHtml = exports.applyInjectTags = void 0;
30
+ const path_1 = __importDefault(require("path"));
31
+ const builder_shared_1 = require("@modern-js/builder-shared");
32
+ const lodash_1 = __importDefault(require("@modern-js/utils/lodash"));
33
+ async function getTemplateParameters(entryName, config, assetPrefix) {
34
+ const { applyOptionsChain } = await Promise.resolve().then(() => __importStar(require('@modern-js/utils')));
35
+ const { mountId, templateParameters, templateParametersByEntries } = config.html;
36
+ const meta = await (0, builder_shared_1.getMetaTags)(entryName, config);
37
+ const title = (0, builder_shared_1.getTitle)(entryName, config);
38
+ const templateParams = (templateParametersByEntries === null || templateParametersByEntries === void 0 ? void 0 : templateParametersByEntries[entryName]) || templateParameters;
39
+ const baseParameters = {
40
+ meta,
41
+ title,
42
+ mountId,
43
+ entryName,
44
+ assetPrefix,
45
+ };
46
+ return (compilation, assets, assetTags, pluginOptions) => {
47
+ const defaultOptions = {
48
+ compilation,
49
+ webpackConfig: compilation.options,
50
+ htmlWebpackPlugin: {
51
+ tags: assetTags,
52
+ files: assets,
53
+ options: pluginOptions,
54
+ },
55
+ ...baseParameters,
56
+ };
57
+ return applyOptionsChain(defaultOptions, templateParams);
58
+ };
59
+ }
60
+ async function getChunks(entryName, entryValue) {
61
+ const { isPlainObject } = await Promise.resolve().then(() => __importStar(require('@modern-js/utils')));
62
+ const dependOn = [];
63
+ if (isPlainObject(entryValue)) {
64
+ // @ts-expect-error assume entry is an entry object
65
+ dependOn.push(...entryValue.dependOn);
66
+ }
67
+ return [...dependOn, entryName];
68
+ }
69
+ // Determine if the string is a favicon url
70
+ const isFaviconUrl = (str) => str.startsWith('http');
71
+ const applyInjectTags = (api) => {
72
+ api.modifyBundlerChain(async (chain, { HtmlPlugin, CHAIN_ID }) => {
73
+ const config = api.getNormalizedConfig();
74
+ const tags = lodash_1.default.castArray(config.html.tags).filter(Boolean);
75
+ const tagsByEntries = lodash_1.default.mapValues(config.html.tagsByEntries, tags => lodash_1.default.castArray(tags).filter(Boolean));
76
+ const shouldByEntries = lodash_1.default.some(tagsByEntries, 'length');
77
+ // skip if options is empty.
78
+ if (!tags.length && !shouldByEntries) {
79
+ return;
80
+ }
81
+ // dynamic import.
82
+ const { HtmlTagsPlugin } = await Promise.resolve().then(() => __importStar(require('@modern-js/builder-shared')));
83
+ // const { HtmlTagsPlugin } = await import('../webpackPlugins/HtmlTagsPlugin');
84
+ // create shared options used for entry without specified options.
85
+ const sharedOptions = {
86
+ HtmlPlugin,
87
+ append: true,
88
+ hash: false,
89
+ publicPath: true,
90
+ tags,
91
+ };
92
+ // apply only one webpack plugin if `html.tagsByEntries` is empty.
93
+ if (tags.length && !shouldByEntries) {
94
+ chain
95
+ .plugin(CHAIN_ID.PLUGIN.HTML_TAGS)
96
+ .use(HtmlTagsPlugin, [sharedOptions]);
97
+ return;
98
+ }
99
+ // apply webpack plugin for each entries.
100
+ for (const [entry, filename] of Object.entries(api.getHTMLPaths())) {
101
+ const opts = { ...sharedOptions, includes: [filename] };
102
+ entry in tagsByEntries && (opts.tags = tagsByEntries[entry]);
103
+ chain
104
+ .plugin(`${CHAIN_ID.PLUGIN.HTML_TAGS}#${entry}`)
105
+ .use(HtmlTagsPlugin, [opts]);
106
+ }
107
+ });
108
+ };
109
+ exports.applyInjectTags = applyInjectTags;
110
+ const builderPluginHtml = () => ({
111
+ name: 'builder-plugin-html',
112
+ setup(api) {
113
+ const routesInfo = [];
114
+ api.modifyBundlerChain(async (chain, { HtmlPlugin, isProd, CHAIN_ID, target }) => {
115
+ const config = api.getNormalizedConfig();
116
+ // if html is disabled or target is server, skip html plugin
117
+ if ((0, builder_shared_1.isHtmlDisabled)(config, target)) {
118
+ return;
119
+ }
120
+ const { removeTailSlash, applyOptionsChain } = await Promise.resolve().then(() => __importStar(require('@modern-js/utils')));
121
+ const minify = (0, builder_shared_1.getMinify)(isProd, config);
122
+ const assetPrefix = removeTailSlash(chain.output.get('publicPath') || '');
123
+ const entries = chain.entryPoints.entries() || {};
124
+ const entryNames = Object.keys(entries);
125
+ const htmlPaths = api.getHTMLPaths();
126
+ const faviconUrls = [];
127
+ await Promise.all(entryNames.map(async (entryName, index) => {
128
+ const entryValue = entries[entryName].values();
129
+ const chunks = await getChunks(entryName, entryValue);
130
+ const inject = (0, builder_shared_1.getInject)(entryName, config);
131
+ const favicon = (0, builder_shared_1.getFavicon)(entryName, config);
132
+ const filename = htmlPaths[entryName];
133
+ const template = (0, builder_shared_1.getTemplatePath)(entryName, config);
134
+ const templateParameters = await getTemplateParameters(entryName, config, assetPrefix);
135
+ const pluginOptions = {
136
+ chunks,
137
+ inject,
138
+ minify,
139
+ filename,
140
+ template,
141
+ templateParameters,
142
+ };
143
+ if (favicon) {
144
+ if (isFaviconUrl(favicon)) {
145
+ faviconUrls.push({
146
+ filename,
147
+ url: favicon,
148
+ });
149
+ }
150
+ else {
151
+ // HTMLWebpackPlugin only support favicon file path
152
+ pluginOptions.favicon = favicon;
153
+ }
154
+ }
155
+ const finalOptions = applyOptionsChain(pluginOptions, config.tools.htmlPlugin, {
156
+ entryName,
157
+ entryValue,
158
+ });
159
+ routesInfo.push({
160
+ urlPath: index === 0 ? '/' : `/${entryName}`,
161
+ entryName,
162
+ entryPath: filename,
163
+ isSPA: true,
164
+ });
165
+ chain
166
+ .plugin(`${CHAIN_ID.PLUGIN.HTML}-${entryName}`)
167
+ .use(HtmlPlugin, [finalOptions]);
168
+ }));
169
+ if (config.html) {
170
+ const { appIcon, crossorigin } = config.html;
171
+ if (crossorigin) {
172
+ const { HtmlCrossOriginPlugin } = await Promise.resolve().then(() => __importStar(require('@modern-js/builder-shared')));
173
+ const formattedCrossorigin = crossorigin === true ? 'anonymous' : crossorigin;
174
+ chain
175
+ .plugin(CHAIN_ID.PLUGIN.HTML_CROSS_ORIGIN)
176
+ .use(HtmlCrossOriginPlugin, [
177
+ { crossOrigin: formattedCrossorigin, HtmlPlugin },
178
+ ]);
179
+ // todo: not support in rspack
180
+ // @ts-expect-error
181
+ chain.output.crossOriginLoading(formattedCrossorigin);
182
+ }
183
+ if (faviconUrls.length) {
184
+ const { HtmlFaviconUrlPlugin } = await Promise.resolve().then(() => __importStar(require('@modern-js/builder-shared')));
185
+ chain
186
+ .plugin(CHAIN_ID.PLUGIN.FAVICON_URL)
187
+ .use(HtmlFaviconUrlPlugin, [{ faviconUrls, HtmlPlugin }]);
188
+ }
189
+ if (appIcon) {
190
+ const { HtmlAppIconPlugin } = await Promise.resolve().then(() => __importStar(require('@modern-js/builder-shared')));
191
+ const distDir = (0, builder_shared_1.getDistPath)(config.output, 'image');
192
+ const iconPath = path_1.default.isAbsolute(appIcon)
193
+ ? appIcon
194
+ : path_1.default.join(api.context.rootPath, appIcon);
195
+ chain
196
+ .plugin(CHAIN_ID.PLUGIN.APP_ICON)
197
+ .use(HtmlAppIconPlugin, [{ iconPath, distDir, HtmlPlugin }]);
198
+ }
199
+ }
200
+ });
201
+ api.onBeforeStartDevServer(async () => {
202
+ const { fs, ROUTE_SPEC_FILE } = await Promise.resolve().then(() => __importStar(require('@modern-js/utils')));
203
+ const routeFilePath = path_1.default.join(api.context.distPath, ROUTE_SPEC_FILE);
204
+ // generate a basic route.json for modern.js dev server
205
+ // if the framework has already generate a route.json, do nothing
206
+ if (!(await (0, builder_shared_1.isFileExists)(routeFilePath)) && routesInfo.length) {
207
+ await fs.outputFile(routeFilePath, JSON.stringify({ routes: routesInfo }, null, 2));
208
+ }
209
+ });
210
+ (0, exports.applyInjectTags)(api);
211
+ },
212
+ });
213
+ exports.builderPluginHtml = builderPluginHtml;
@@ -1,4 +1,5 @@
1
1
  export declare const plugins: {
2
+ html: () => Promise<import("@modern-js/builder-shared").DefaultBuilderPlugin>;
2
3
  cleanOutput: () => Promise<import("@modern-js/builder-shared").DefaultBuilderPlugin>;
3
4
  startUrl: () => Promise<import("@modern-js/builder-shared").DefaultBuilderPlugin>;
4
5
  fileSize: () => Promise<import("@modern-js/builder-shared").DefaultBuilderPlugin>;
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.plugins = void 0;
27
27
  const builder_shared_1 = require("@modern-js/builder-shared");
28
28
  exports.plugins = {
29
+ html: () => Promise.resolve().then(() => __importStar(require('./html'))).then(m => m.builderPluginHtml()),
29
30
  cleanOutput: () => Promise.resolve().then(() => __importStar(require('./cleanOutput'))).then(m => m.builderPluginCleanOutput()),
30
31
  startUrl: () => Promise.resolve().then(() => __importStar(require('./startUrl'))).then(m => m.builderPluginStartUrl()),
31
32
  fileSize: () => Promise.resolve().then(() => __importStar(require('./fileSize'))).then(m => m.builderPluginFileSize()),
@@ -44,7 +44,7 @@ function builderPluginStartUrl() {
44
44
  return;
45
45
  }
46
46
  const config = api.getNormalizedConfig();
47
- const { https, startUrl } = config.dev;
47
+ const { https, startUrl, beforeStartUrl } = config.dev;
48
48
  if (!startUrl) {
49
49
  return;
50
50
  }
@@ -56,16 +56,25 @@ function builderPluginStartUrl() {
56
56
  else {
57
57
  urls.push(...lodash_1.default.castArray(startUrl).map(item => (0, exports.replacePlaceholder)(item, port)));
58
58
  }
59
+ const { ensureArray } = await Promise.resolve().then(() => __importStar(require('@modern-js/utils')));
59
60
  const { openBrowser } = await Promise.resolve().then(() => __importStar(require('@modern-js/builder-shared')));
60
- for (const url of urls) {
61
- /**
62
- * If a URL has been opened in current process, we will not open it again.
63
- * It can prevent opening the same URL multiple times.
64
- */
65
- if (!openedURLs.includes(url)) {
66
- await openBrowser(url);
67
- openedURLs.push(url);
61
+ const openUrls = () => {
62
+ for (const url of urls) {
63
+ /**
64
+ * If a URL has been opened in current process, we will not open it again.
65
+ * It can prevent opening the same URL multiple times.
66
+ */
67
+ if (!openedURLs.includes(url)) {
68
+ openBrowser(url);
69
+ openedURLs.push(url);
70
+ }
68
71
  }
72
+ };
73
+ if (beforeStartUrl) {
74
+ Promise.all(ensureArray(beforeStartUrl).map(fn => fn())).then(openUrls);
75
+ }
76
+ else {
77
+ openUrls();
69
78
  }
70
79
  });
71
80
  },
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "engines": {
15
15
  "node": ">=14.0.0"
16
16
  },
17
- "version": "2.5.0",
17
+ "version": "2.6.0",
18
18
  "jsnext:source": "./src/index.ts",
19
19
  "types": "./dist/index.d.ts",
20
20
  "main": "./dist/index.js",
@@ -27,8 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@svgr/webpack": "6.5.1",
30
- "@modern-js/builder-shared": "2.5.0",
31
- "@modern-js/utils": "2.5.0"
30
+ "@modern-js/builder-shared": "2.6.0",
31
+ "@modern-js/utils": "2.6.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@babel/core": "7.18.0",
@@ -36,7 +36,7 @@
36
36
  "@types/babel__preset-env": "^7.9.2",
37
37
  "@types/node": "^14",
38
38
  "typescript": "^4",
39
- "@scripts/vitest-config": "2.5.0"
39
+ "@scripts/vitest-config": "2.6.0"
40
40
  },
41
41
  "publishConfig": {
42
42
  "registry": "https://registry.npmjs.org/",