Skip to content

Commit 4f35b35

Browse files
Vite WP React: Externalize @wordpress/* dependencies dynamically for production (#141)
* Vite WP React: Use externalize wp deps dynamically for production * Add changelog * Update wp-packages.ts
1 parent 9adfd48 commit 4f35b35

File tree

5 files changed

+119
-19
lines changed

5 files changed

+119
-19
lines changed

.changeset/calm-hotels-explain.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@wpsocio/vite-wp-react": patch
3+
---
4+
5+
Externalize WP deps dynamically to ensure new dependencies are not left out
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
import rollupGlobals from 'rollup-plugin-external-globals';
22
import type { PluginOption } from 'vite';
3-
import viteExternal from 'vite-plugin-external';
4-
import { WP_EXTERNAL_PACKAGES } from '../utils/index.js';
3+
import viteExternalPlugin from 'vite-plugin-external';
4+
import {
5+
BUNDLED_WP_PACKAGES,
6+
NON_WP_PACKAGES,
7+
WP_EXTERNAL_PACKAGES,
8+
dashToCamelCase,
9+
} from '../utils/index.js';
10+
11+
const viteExternal =
12+
// viteExternalPlugin is not typed well
13+
viteExternalPlugin as unknown as (typeof viteExternalPlugin)['default'];
14+
15+
function shouldExternalizePakage(name: string) {
16+
if (BUNDLED_WP_PACKAGES.includes(name)) {
17+
return false;
18+
}
19+
20+
const isWpPackage = name.startsWith('@wordpress/');
21+
22+
const isNonWpPackage = name in NON_WP_PACKAGES;
23+
24+
return isWpPackage || isNonWpPackage;
25+
}
526

627
/**
728
* Updates the vite config to externalize all WordPress packages.
@@ -10,28 +31,59 @@ export const externalizeWpPackages = (): PluginOption => {
1031
return [
1132
{
1233
name: 'vwpr:externalize-wp-packages',
13-
config() {
34+
config(config, { command }) {
35+
// We need to run this only for the build command
36+
if (command !== 'build') {
37+
return {};
38+
}
1439
return {
1540
build: {
1641
rollupOptions: {
17-
external: Object.keys(WP_EXTERNAL_PACKAGES),
42+
external(source) {
43+
return shouldExternalizePakage(source);
44+
},
1845
plugins: [
1946
/**
2047
* Add the plugin to rollup to ensure react imports don't end up in the bundle
2148
* framer-motion causes the issue by using namespace imports
2249
*
2350
* @see https://github.com/vitejs/vite-plugin-react/issues/3
2451
*/
25-
rollupGlobals(WP_EXTERNAL_PACKAGES),
52+
rollupGlobals((name) => {
53+
if (!shouldExternalizePakage(name)) {
54+
return '';
55+
}
56+
57+
if (name in NON_WP_PACKAGES) {
58+
return NON_WP_PACKAGES[name];
59+
}
60+
61+
if (name.startsWith('@wordpress/')) {
62+
const variable = dashToCamelCase(
63+
name.replace('@wordpress/', ''),
64+
);
65+
return `wp.${variable}`;
66+
}
67+
68+
return '';
69+
}),
2670
],
2771
},
2872
},
2973
};
3074
},
3175
},
32-
// @ts-ignore - viteExternal is not typed well
76+
/**
77+
* We need this plugin only during development
78+
* To ensure that module aliases are created for external packages
79+
*
80+
* Change the externals list to dynamic if the below issue is resolved
81+
* @see https://github.com/fengxinming/vite-plugins/issues/44
82+
*/
3383
viteExternal({
34-
externals: WP_EXTERNAL_PACKAGES,
84+
development: {
85+
externals: WP_EXTERNAL_PACKAGES,
86+
},
3587
}),
3688
];
3789
};

tools/vite-wp-react/src/plugins/extract-wp-dependencies.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import fs from 'node:fs';
22
import path from 'node:path';
33
import type { Plugin, ResolvedConfig } from 'vite';
44
import {
5+
BUNDLED_WP_PACKAGES,
6+
NON_WP_PACKAGES,
7+
PACKAGE_HANDLES,
58
type ScanDependenciesOptions,
6-
WP_EXTERNAL_PACKAGES,
79
scanDependencies,
810
} from '../utils/index.js';
911

@@ -12,6 +14,10 @@ export type ExtractWpDependenciesOptions = {
1214
fileName?: string;
1315
} & Pick<ScanDependenciesOptions, 'plugins' | 'normalizePath'>;
1416

17+
const dependenciesToScan = new RegExp(
18+
`^((${Object.keys(NON_WP_PACKAGES).join('|')})|@wordpress/.+)$`,
19+
);
20+
1521
/**
1622
* Extract external dependencies from the bundle.
1723
*/
@@ -29,9 +35,18 @@ export const extractWpDependencies = ({
2935
async buildStart(options) {
3036
scanDependencies({
3137
absWorkingDir: config.root,
32-
dependenciesToScan: Object.keys(WP_EXTERNAL_PACKAGES),
38+
dependenciesToScan,
39+
excludeDependencies: BUNDLED_WP_PACKAGES,
3340
input: options.input,
34-
normalizePath: (path) => path.replace(/^@wordpress\//, 'wp-'),
41+
normalizePath: (path) => {
42+
if (path in PACKAGE_HANDLES) {
43+
return PACKAGE_HANDLES[path];
44+
}
45+
46+
const _path = path.replace(/^@wordpress\//, 'wp-');
47+
48+
return _path;
49+
},
3550
onComplete: (source) => {
3651
// this.emitFile is available only in build mode
3752
config.command === 'build'

tools/vite-wp-react/src/utils/scan-dependencies.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@ export const IMPORTS_TO_IGNORE =
99
export type ScanDependenciesOptions = {
1010
absWorkingDir: string;
1111
input?: InputOption;
12-
dependenciesToScan: Array<string>;
12+
dependenciesToScan: RegExp;
1313
normalizePath?: (path: string) => string;
1414
plugins?: Array<EsBuildPlugin>;
1515
onComplete?: (data: string) => void;
16+
excludeDependencies?: Array<string>;
1617
};
1718

1819
/**
1920
* Scan dependencies
2021
*/
2122
export async function scanDependencies({
2223
absWorkingDir,
23-
dependenciesToScan = [],
24+
dependenciesToScan,
2425
input,
2526
normalizePath,
2627
plugins = [],
2728
onComplete,
29+
excludeDependencies = [],
2830
}: ScanDependenciesOptions) {
2931
const dependencies: Record<string, Array<string>> = {};
3032
const entries: Array<string> = [];
@@ -42,9 +44,6 @@ export async function scanDependencies({
4244
throw new Error('No entry points found');
4345
}
4446

45-
// Create a filter for the dependencies we want to scan
46-
const filter = new RegExp(`^(${dependenciesToScan.join('|')})$`);
47-
4847
try {
4948
await Promise.all(
5049
validEntries.map((absoluteEntry) => {
@@ -68,16 +67,22 @@ export async function scanDependencies({
6867
{
6968
name: 'scan-dependencies',
7069
setup(build) {
71-
build.onResolve({ filter }, (args) => ({
70+
build.onResolve({ filter: dependenciesToScan }, (args) => ({
7271
path: args.path,
7372
namespace: 'scan-dependencies',
7473
}));
74+
7575
build.onLoad(
7676
{ filter: /.*/, namespace: 'scan-dependencies' },
7777
(args) => {
78-
dependencies[entry].push(
79-
normalizePath ? normalizePath(args.path) : args.path,
80-
);
78+
if (!excludeDependencies.includes(args.path)) {
79+
const dependency = normalizePath
80+
? normalizePath(args.path)
81+
: args.path;
82+
83+
dependencies[entry].push(dependency);
84+
}
85+
8186
return {
8287
contents: 'exports.ok = true;',
8388
loader: 'js',

tools/vite-wp-react/src/utils/wp-packages.ts

+23
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,31 @@ export const NON_WP_PACKAGES: Record<string, string> = {
6767
'react-dom': 'ReactDOM',
6868
backbone: 'Backbone',
6969
lodash: 'lodash',
70+
'lodash-es': 'lodash',
71+
'react/jsx-runtime': 'ReactJSXRuntime',
72+
'react-refresh/runtime': 'ReactRefreshRuntime',
7073
};
7174

75+
export const PACKAGE_HANDLES: Record<string, string> = {
76+
'lodash-es': 'lodash',
77+
'react/jsx-runtime': 'react-jsx-runtime',
78+
'react-refresh/runtime': 'wp-react-refresh-runtime',
79+
};
80+
81+
/**
82+
* WordPress packages that are bundled.
83+
*
84+
* This list comes from Gutenberg
85+
* @see https://github.com/WordPress/gutenberg/blob/313246a01f18e504dabd8313e7eacca728332bcd/packages/dependency-extraction-webpack-plugin/lib/util.js#L6
86+
*/
87+
export const BUNDLED_WP_PACKAGES = [
88+
'@wordpress/dataviews',
89+
'@wordpress/icons',
90+
'@wordpress/interface',
91+
'@wordpress/sync',
92+
'@wordpress/undo-manager',
93+
];
94+
7295
export const WP_EXTERNAL_PACKAGES: Record<string, string> = {
7396
...NON_WP_PACKAGES,
7497
...WP_PACKAGES,

0 commit comments

Comments
 (0)