Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(builder): support inline assets by file size #4563

Merged
merged 2 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/rare-wolves-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@modern-js/builder-shared': patch
'@modern-js/builder': patch
'@modern-js/utils': patch
'@modern-js/core': patch
---

feat(builder): support inline assets by file size

feat(builder): 支持基于文件体积来内联资源
Original file line number Diff line number Diff line change
Expand Up @@ -1493,9 +1493,10 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when prod
"htmlPlugin": [Function],
"inlinedAssets": Set {},
"name": "InlineChunkHtmlPlugin",
"tests": [
"scriptTests": [
/builder-runtime\\(\\[\\.\\]\\.\\+\\)\\?\\\\\\.js\\$/,
],
"styleTests": [],
},
],
"resolve": {
Expand Down
5 changes: 3 additions & 2 deletions packages/builder/builder-shared/src/mergeBuilderConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from '@modern-js/utils/lodash';
import { isOverriddenConfigKey } from '@modern-js/utils';

export const mergeBuilderConfig = <T>(...configs: T[]): T =>
_.mergeWith(
Expand All @@ -11,8 +12,8 @@ export const mergeBuilderConfig = <T>(...configs: T[]): T =>
return undefined;
}

// always use source override target, if target defined.
if (['removeConsole'].includes(key)) {
// Some keys should use source to override target
if (isOverriddenConfigKey(key)) {
return source ?? target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
* modified from https://github.com/facebook/create-react-app/blob/master/packages/react-dev-utils/InlineChunkHtmlPlugin.js
*/
import { join } from 'path';
import { isString } from '@modern-js/utils';
import { isFunction, isString } from '@modern-js/utils';
import { addTrailingSlash } from '../utils';
import type { Compiler, Compilation } from 'webpack';
import type HtmlWebpackPlugin from 'html-webpack-plugin';
import type { HtmlTagObject } from 'html-webpack-plugin';
import { COMPILATION_PROCESS_STAGE, getPublicPathFromCompiler } from './util';

export type InlineChunkTestFunction = (params: {
size: number;
name: string;
}) => boolean;
export type InlineChunkTest = RegExp | InlineChunkTestFunction;

export type InlineChunkHtmlPluginOptions = {
tests: RegExp[];
styleTests: InlineChunkTest[];
scriptTests: InlineChunkTest[];
distPath: {
js?: string;
css?: string;
Expand All @@ -24,7 +31,9 @@ export type InlineChunkHtmlPluginOptions = {
export class InlineChunkHtmlPlugin {
name: string;

tests: RegExp[];
styleTests: InlineChunkTest[];

scriptTests: InlineChunkTest[];

distPath: InlineChunkHtmlPluginOptions['distPath'];

Expand All @@ -34,10 +43,11 @@ export class InlineChunkHtmlPlugin {

constructor(
htmlPlugin: typeof HtmlWebpackPlugin,
{ tests, distPath }: InlineChunkHtmlPluginOptions,
{ styleTests, scriptTests, distPath }: InlineChunkHtmlPluginOptions,
) {
this.name = 'InlineChunkHtmlPlugin';
this.tests = tests;
this.styleTests = styleTests;
this.scriptTests = scriptTests;
this.distPath = distPath;
this.inlinedAssets = new Set();
this.htmlPlugin = htmlPlugin;
Expand Down Expand Up @@ -80,28 +90,42 @@ export class InlineChunkHtmlPlugin {
return source;
}

matchTests(name: string, source: string, tests: InlineChunkTest[]) {
return tests.some(test => {
if (isFunction(test)) {
const size = source.length;
return test({ name, size });
}
return test.exec(name);
});
}

getInlinedScriptTag(
publicPath: string,
tag: HtmlTagObject,
compilation: Compilation,
) {
const { assets } = compilation;

// No need to inline scripts with src attribute
if (!(tag?.attributes.src && isString(tag.attributes.src))) {
return tag;
}

const { src, ...otherAttrs } = tag.attributes;
const scriptName = publicPath ? src.replace(publicPath, '') : src;

if (!this.tests.some(test => test.exec(scriptName))) {
return tag;
}
// If asset is not found, skip it
const asset = assets[scriptName];
if (asset == null) {
return tag;
}

const source = asset.source().toString();
const shouldInline = this.matchTests(scriptName, source, this.scriptTests);
if (!shouldInline) {
return tag;
}

const ret = {
tagName: 'script',
Expand Down Expand Up @@ -130,6 +154,7 @@ export class InlineChunkHtmlPlugin {
) {
const { assets } = compilation;

// No need to inline styles with href attribute
if (!(tag.attributes.href && isString(tag.attributes.href))) {
return tag;
}
Expand All @@ -138,13 +163,18 @@ export class InlineChunkHtmlPlugin {
? tag.attributes.href.replace(publicPath, '')
: tag.attributes.href;

if (!this.tests.some(test => test.exec(linkName))) {
// If asset is not found, skip it
const asset = assets[linkName];
if (asset == null) {
return tag;
}

const asset = assets[linkName];

const source = asset.source().toString();
const shouldInline = this.matchTests(linkName, source, this.styleTests);
if (!shouldInline) {
return tag;
}

const ret = {
tagName: 'style',
innerHTML: this.updateSourceMappingURL({
Expand Down
5 changes: 4 additions & 1 deletion packages/builder/builder-shared/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export { HtmlCrossOriginPlugin } from './HtmlCrossOriginPlugin';
export { HtmlNoncePlugin } from './HtmlNoncePlugin';
export { HtmlAppIconPlugin } from './HtmlAppIconPlugin';
export { HtmlFaviconUrlPlugin, type FaviconUrls } from './HtmlFaviconUrlPlugin';
export { InlineChunkHtmlPlugin } from './InlineChunkHtmlPlugin';
export {
InlineChunkHtmlPlugin,
type InlineChunkTest,
} from './InlineChunkHtmlPlugin';
export { AssetsRetryPlugin } from './AssetsRetryPlugin';
export { CheckSyntaxPlugin } from './CheckSyntaxPlugin';
export { HtmlNetworkPerformancePlugin } from './HtmlNetworkPerformancePlugin';
Expand Down
10 changes: 8 additions & 2 deletions packages/builder/builder-shared/src/schema/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export const SvgDefaultExportSchema: ZodType<SvgDefaultExport> = z.enum([
'url',
]);

const inlineSchema = z.union([
z.boolean(),
z.instanceof(RegExp),
z.anyFunction(),
]);

export const sharedOutputConfigSchema = z.partialObj({
distPath: DistPathConfigSchema,
filename: FilenameConfigSchema,
Expand All @@ -100,8 +106,8 @@ export const sharedOutputConfigSchema = z.partialObj({
enableAssetFallback: z.boolean(),
enableLatestDecorators: z.boolean(),
enableCssModuleTSDeclaration: z.boolean(),
enableInlineScripts: z.union([z.boolean(), z.instanceof(RegExp)]),
enableInlineStyles: z.union([z.boolean(), z.instanceof(RegExp)]),
enableInlineScripts: inlineSchema,
enableInlineStyles: inlineSchema,
externals: z.any(),
overrideBrowserslist: z.union([
z.array(z.string()),
Expand Down
9 changes: 5 additions & 4 deletions packages/builder/builder-shared/src/types/config/output.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { InlineChunkTest } from '../../plugins/InlineChunkHtmlPlugin';
import type { BuilderTarget } from '../builder';
import type { CrossOrigin } from './html';
import type { Externals } from 'webpack';
Expand Down Expand Up @@ -261,11 +262,11 @@ export interface SharedOutputConfig {
/**
* Whether to inline output scripts files (.js files) into HTML with `<script>` tags.
*/
enableInlineScripts?: boolean | RegExp;
enableInlineScripts?: boolean | InlineChunkTest;
/**
* Whether to inline output style files (.css files) into html with `<style>` tags.
*/
enableInlineStyles?: boolean | RegExp;
enableInlineStyles?: boolean | InlineChunkTest;
/**
* Specifies the range of target browsers that the project is compatible with.
* This value will be used by [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) and
Expand Down Expand Up @@ -300,8 +301,8 @@ export interface NormalizedSharedOutputConfig extends SharedOutputConfig {
enableAssetFallback: boolean;
enableLatestDecorators: boolean;
enableCssModuleTSDeclaration: boolean;
enableInlineScripts: boolean | RegExp;
enableInlineStyles: boolean | RegExp;
enableInlineScripts: boolean | InlineChunkTest;
enableInlineStyles: boolean | InlineChunkTest;
svgDefaultExport: SvgDefaultExport;
cssModules: {
exportLocalsConvention: CssModuleLocalsConvention;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1833,9 +1833,10 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when produ
"htmlPlugin": [Function],
"inlinedAssets": Set {},
"name": "InlineChunkHtmlPlugin",
"tests": [
"scriptTests": [
/builder-runtime\\(\\[\\.\\]\\.\\+\\)\\?\\\\\\.js\\$/,
],
"styleTests": [],
},
MiniCssExtractPlugin {
"_sortedModulesCache": WeakMap {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ exports[`plugins/inlineChunk > should add InlineChunkHtmlPlugin properly by defa
"htmlPlugin": [Function],
"inlinedAssets": Set {},
"name": "InlineChunkHtmlPlugin",
"tests": [
"scriptTests": [
/builder-runtime\\(\\[\\.\\]\\.\\+\\)\\?\\\\\\.js\\$/,
],
"styleTests": [],
},
],
}
Expand Down Expand Up @@ -106,7 +107,8 @@ exports[`plugins/inlineChunk > should use proper plugin options when disableInli
"htmlPlugin": [Function],
"inlinedAssets": Set {},
"name": "InlineChunkHtmlPlugin",
"tests": [],
"scriptTests": [],
"styleTests": [],
},
],
}
Expand Down Expand Up @@ -161,10 +163,11 @@ exports[`plugins/inlineChunk > should use proper plugin options when enableInlin
"htmlPlugin": [Function],
"inlinedAssets": Set {},
"name": "InlineChunkHtmlPlugin",
"tests": [
/\\\\\\.js\\$/,
"scriptTests": [
/\\\\\\.\\(js\\|mjs\\|cjs\\|jsx\\)\\$/,
/builder-runtime\\(\\[\\.\\]\\.\\+\\)\\?\\\\\\.js\\$/,
],
"styleTests": [],
},
],
}
Expand Down Expand Up @@ -219,10 +222,12 @@ exports[`plugins/inlineChunk > should use proper plugin options when enableInlin
"htmlPlugin": [Function],
"inlinedAssets": Set {},
"name": "InlineChunkHtmlPlugin",
"tests": [
/\\\\\\.css\\$/,
"scriptTests": [
/builder-runtime\\(\\[\\.\\]\\.\\+\\)\\?\\\\\\.js\\$/,
],
"styleTests": [
/\\\\\\.css\\$/,
],
},
],
}
Expand Down
19 changes: 12 additions & 7 deletions packages/builder/builder/src/plugins/inlineChunk.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {
pick,
JS_REGEX,
CSS_REGEX,
RUNTIME_CHUNK_NAME,
isHtmlDisabled,
DefaultBuilderPlugin,
type InlineChunkTest,
} from '@modern-js/builder-shared';

export const builderPluginInlineChunk = (): DefaultBuilderPlugin => ({
Expand All @@ -28,22 +31,23 @@ export const builderPluginInlineChunk = (): DefaultBuilderPlugin => ({
enableInlineScripts,
} = config.output;

const tests: RegExp[] = [];
const scriptTests: InlineChunkTest[] = [];
const styleTests: InlineChunkTest[] = [];

if (enableInlineScripts) {
tests.push(
enableInlineScripts === true ? /\.js$/ : enableInlineScripts,
scriptTests.push(
enableInlineScripts === true ? JS_REGEX : enableInlineScripts,
);
}

if (enableInlineStyles) {
tests.push(
enableInlineStyles === true ? /\.css$/ : enableInlineStyles,
styleTests.push(
enableInlineStyles === true ? CSS_REGEX : enableInlineStyles,
);
}

if (!disableInlineRuntimeChunk) {
tests.push(
scriptTests.push(
// RegExp like /builder-runtime([.].+)?\.js$/
// matches builder-runtime.js and builder-runtime.123456.js
new RegExp(`${RUNTIME_CHUNK_NAME}([.].+)?\\.js$`),
Expand All @@ -53,7 +57,8 @@ export const builderPluginInlineChunk = (): DefaultBuilderPlugin => ({
chain.plugin(CHAIN_ID.PLUGIN.INLINE_HTML).use(InlineChunkHtmlPlugin, [
HtmlPlugin,
{
tests,
styleTests,
scriptTests,
distPath: pick(config.output.distPath, ['js', 'css']),
},
]);
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/core/src/utils/mergeConfig.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { ensureArray } from '@modern-js/utils';
import { ensureArray, isOverriddenConfigKey } from '@modern-js/utils';
import { mergeWith, isFunction } from '@modern-js/utils/lodash';
import { UserConfig, NormalizedConfig } from '../types';

export const mergeConfig = <ExtendConfig extends Record<string, any>>(
configs: Array<UserConfig<ExtendConfig> | NormalizedConfig<ExtendConfig>>,
): NormalizedConfig<ExtendConfig> =>
mergeWith({}, ...configs, (target: any, source: any, key: string) => {
// Do not use the following merge logic for source.designSystem and tools.tailwind(css)
// Do not use the following merge logic for some keys
if (
key === 'designSystem' ||
(key === 'tailwindcss' && typeof source === 'object')
) {
return mergeWith({}, target ?? {}, source ?? {});
}

// Some keys should use source to override target
if (isOverriddenConfigKey(key)) {
return source ?? target;
}

if (Array.isArray(target) || Array.isArray(source)) {
if (target === undefined) {
return source;
Expand Down
Loading