Skip to content

Commit 18ccb5a

Browse files
committed
feat: add custom plugin to auto extract wp dependencies
Also emit a php file for the enqueue to pick up and work with the said dependencies.
1 parent af1650e commit 18ccb5a

File tree

10 files changed

+448
-25
lines changed

10 files changed

+448
-25
lines changed

examples/tryouts/package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@
99
"bootstrap": "wpackio-scripts bootstrap",
1010
"build": "wpackio-scripts build",
1111
"start": "wpackio-scripts start",
12-
"archive": "wpackio-scripts pack"
12+
"archive": "wpackio-scripts pack",
13+
"serve": "serve -l 8080"
1314
},
1415
"keywords": [],
1516
"author": "",
1617
"license": "ISC",
1718
"dependencies": {
18-
"@wpackio/entrypoint": "^5.0.0"
19+
"@wordpress/i18n": "^3.19.2",
20+
"@wpackio/entrypoint": "^5.0.0",
21+
"jQuery": "^1.7.4",
22+
"lodash": "^4.17.21",
23+
"react": "^17.0.2",
24+
"react-dom": "^17.0.2"
1925
},
2026
"devDependencies": {
2127
"@wpackio/scripts": "^5.0.0",
2228
"less": "^4.1.1",
23-
"postcss": "^8.2.12"
29+
"postcss": "^8.2.12",
30+
"serve": "^11.3.2"
2431
}
2532
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module 'json2php' {
2+
function json2php(input: any): string;
3+
export = json2php;
4+
}

packages/scripts/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@types/babel__code-frame": "^7.0.2",
3030
"@types/browser-sync": "^2.26.1",
3131
"@types/gradient-string": "^1.1.1",
32+
"@types/lockfile": "^1.0.1",
3233
"@types/prompts": "^2.0.10",
3334
"@types/update-notifier": "^5.0.0",
3435
"@types/webpack": "^4.41.8",
@@ -55,7 +56,9 @@
5556
"find-up": "^5.0.0",
5657
"gradient-string": "^1.2.0",
5758
"handlebars": "^4.7.7",
59+
"json2php": "^0.0.4",
5860
"less-loader": "7.3.0",
61+
"lockfile": "^1.0.4",
5962
"log-symbols": "^4.0.0",
6063
"make-dir": "^3.1.0",
6164
"mini-css-extract-plugin": "^1.5.0",

packages/scripts/src/config/WebpackConfigHelper.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import {
33
PresetOptions,
44
} from '@wpackio/babel-preset-base/lib/preset';
55
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
6-
import fs from 'fs';
6+
77
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
88
import path from 'path';
99
import slugify from 'slugify';
1010
import TimeFixPlugin from 'time-fix-plugin';
1111
import webpack from 'webpack';
1212
import WebpackAssetsManifest from 'webpack-assets-manifest';
1313
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
14+
import { DependencyExtractionWebpackPlugin } from '../plugins/DependencyExtractionWebpackPlugin';
1415

1516
import { WatchMissingNodeModulesPlugin } from '../dev-utils';
1617
import { WpackioError } from '../errors/WpackioError';
@@ -27,6 +28,7 @@ import {
2728
getFileLoaderForJsAndStyleAssets,
2829
getStyleLoaderUses,
2930
} from './loaderHelpers';
31+
import { hasTypeScript } from '../dev-utils/ops';
3032

3133
interface NormalizedEntry {
3234
[x: string]: string[];
@@ -69,23 +71,6 @@ interface CommonWebpackConfig {
6971
infrastructureLogging: any;
7072
}
7173

72-
/**
73-
* Check if file exists or not using fs API.
74-
*/
75-
export function fileExists(filepath: string): boolean {
76-
try {
77-
// tslint:disable-next-line:non-literal-fs-path
78-
return fs.statSync(filepath).isFile();
79-
} catch (_) {
80-
return false;
81-
}
82-
}
83-
84-
export function hasTypeScript(cwd: string): [boolean, string] {
85-
const tsconfigPath = path.resolve(cwd, './tsconfig.json');
86-
return [fileExists(tsconfigPath), tsconfigPath];
87-
}
88-
8974
/**
9075
* A helper class to get different configuration of webpack.
9176
*/
@@ -336,6 +321,15 @@ export class WebpackConfigHelper {
336321
throw new WpackioError(e);
337322
}
338323
}
324+
325+
// Add wordpress dependency extract plugin
326+
plugins.push(
327+
new DependencyExtractionWebpackPlugin({
328+
gutenbergOptimized: this.file.optimizeForGutenberg ?? false,
329+
appDir: this.appDir,
330+
})
331+
);
332+
339333
// Add development specific plugins
340334
if (this.isDev) {
341335
// Hot Module Replacement

packages/scripts/src/config/project.config.default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export interface EntryConfig {
3131
export interface FileConfig {
3232
name: string;
3333
entry: EntryConfig;
34-
typeWatchFiles?: string[];
3534
hasTypeScript?: boolean;
3635
webpackConfig?:
3736
| webpack.Configuration
@@ -41,6 +40,7 @@ export interface FileConfig {
4140
appDir: string,
4241
isDev: boolean
4342
) => webpack.Configuration);
43+
optimizeForGutenberg?: boolean;
4444
}
4545

4646
export type webpackOptionsOverrideFunction = (
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import lockfile from 'lockfile';
4+
import util from 'util';
5+
import os from 'os';
6+
import crypto from 'crypto';
7+
8+
const lfUnlock = util.promisify(lockfile.unlock);
9+
10+
/**
11+
* Check if file exists or not using fs API.
12+
*/
13+
export function fileExists(filepath: string): boolean {
14+
try {
15+
// tslint:disable-next-line:non-literal-fs-path
16+
return fs.statSync(filepath).isFile();
17+
} catch (_) {
18+
return false;
19+
}
20+
}
21+
22+
/**
23+
* Check whether current working directory is a typescript project.
24+
*
25+
*
26+
* @param cwd Current working directory.
27+
* @returns True if tsconfig is found, false otherwiise.
28+
*/
29+
export function hasTypeScript(cwd: string): [boolean, string] {
30+
const tsconfigPath = path.resolve(cwd, './tsconfig.json');
31+
return [fileExists(tsconfigPath), tsconfigPath];
32+
}
33+
34+
export const WORDPRESS_NAMESPACE = '@wordpress/';
35+
export const BUNDLED_PACKAGES = ['@wordpress/icons', '@wordpress/interface'];
36+
37+
/**
38+
* Given a string, returns a new string with dash separators converted to
39+
* camelCase equivalent. This is not as aggressive as `_.camelCase` in
40+
* converting to uppercase, where Lodash will also capitalize letters
41+
* following numbers.
42+
*
43+
* @see {https://github.com/WordPress/gutenberg/blob/c047e2716149c794eebff3c2c002f66a6f546f59/packages/dependency-extraction-webpack-plugin/lib/util.js#L83}
44+
*
45+
* @param {string} input Input dash-delimited string.
46+
* @return {string} Camel-cased string.
47+
*/
48+
function camelCaseDash(input: string): string {
49+
return input.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
50+
}
51+
52+
/**
53+
* Wpackio specific request to global transformation. This is used in
54+
* @wordpress/dependency-extraction-webpack-plugin.
55+
*
56+
* Unlike the default one, it doesn't transform react/reactdom, moment, jQuery
57+
* etc.
58+
*
59+
* Transform @wordpress dependencies:
60+
* - request `@wordpress/api-fetch` becomes `[ 'wp', 'apiFetch' ]`
61+
* - request `@wordpress/i18n` becomes `[ 'wp', 'i18n' ]`
62+
*
63+
* @param request Module request (the module name in `import from`) to be transformed
64+
* @returns The resulting external definition. Return `undefined`
65+
* to ignore the request. Return `string|string[]` to map the request to an external.
66+
*/
67+
export function wpackioRequestsToExternals(
68+
request: string
69+
): string | string[] | undefined {
70+
if (BUNDLED_PACKAGES.includes(request)) {
71+
return undefined;
72+
}
73+
74+
if (request.startsWith(WORDPRESS_NAMESPACE)) {
75+
return ['wp', camelCaseDash(request.substring(WORDPRESS_NAMESPACE.length))];
76+
}
77+
78+
return undefined;
79+
}
80+
81+
/**
82+
* Default request to global transformation
83+
*
84+
* Transform @wordpress dependencies:
85+
* - request `@wordpress/api-fetch` becomes `[ 'wp', 'apiFetch' ]`
86+
* - request `@wordpress/i18n` becomes `[ 'wp', 'i18n' ]`
87+
*
88+
* @param {string} request Module request (the module name in `import from`) to be transformed
89+
* @return {string|string[]|undefined} The resulting external definition. Return `undefined`
90+
* to ignore the request. Return `string|string[]` to map the request to an external.
91+
*/
92+
export function defaultRequestToExternal(
93+
request: string
94+
): string | string[] | undefined {
95+
switch (request) {
96+
case 'moment':
97+
return request;
98+
99+
case '@babel/runtime/regenerator':
100+
return 'regeneratorRuntime';
101+
102+
case 'lodash':
103+
case 'lodash-es':
104+
return 'lodash';
105+
106+
case 'jquery':
107+
return 'jQuery';
108+
109+
case 'react':
110+
return 'React';
111+
112+
case 'react-dom':
113+
return 'ReactDOM';
114+
115+
default:
116+
return wpackioRequestsToExternals(request);
117+
}
118+
}
119+
120+
/**
121+
* Wpackio specific request to WordPress script handle transformation
122+
*
123+
* Transform @wordpress dependencies:
124+
* - request `@wordpress/i18n` becomes `wp-i18n`
125+
* - request `@wordpress/escape-html` becomes `wp-escape-html`
126+
*
127+
* @param {string} request Module request (the module name in `import from`) to be transformed
128+
* @return {string|undefined} WordPress script handle to map the request to. Return `undefined`
129+
* to use the same name as the module.
130+
*/
131+
export function wpackioRequestToHandle(request: string): string | undefined {
132+
if (request.startsWith(WORDPRESS_NAMESPACE)) {
133+
return `wp-${request.substring(WORDPRESS_NAMESPACE.length)}`;
134+
}
135+
return undefined;
136+
}
137+
138+
/**
139+
* Default request to WordPress script handle transformation
140+
*
141+
* Transform @wordpress dependencies:
142+
* - request `@wordpress/i18n` becomes `wp-i18n`
143+
* - request `@wordpress/escape-html` becomes `wp-escape-html`
144+
*
145+
* @param {string} request Module request (the module name in `import from`) to be transformed
146+
* @return {string|undefined} WordPress script handle to map the request to. Return `undefined`
147+
* to use the same name as the module.
148+
*/
149+
export function defaultRequestToHandle(request: string): undefined | string {
150+
switch (request) {
151+
case '@babel/runtime/regenerator':
152+
return 'wp-polyfill';
153+
154+
case 'lodash-es':
155+
return 'lodash';
156+
default:
157+
return wpackioRequestToHandle(request);
158+
}
159+
}
160+
161+
/**
162+
* Get the name of the file from a file path.
163+
*
164+
* @param name Full path of the filename.
165+
* @returns Just the name of the file.
166+
*/
167+
export function basename(name: string) {
168+
if (!name.includes('/')) {
169+
return name;
170+
}
171+
return name.substr(name.lastIndexOf('/') + 1);
172+
}
173+
174+
function md5(data: crypto.BinaryLike | string) {
175+
return crypto.createHash('md5').update(data).digest('hex');
176+
}
177+
178+
/**
179+
* Build a file path to a lock file in the tmp directory
180+
*
181+
* @param {string} filename
182+
*/
183+
export function getLockFilename(filename: string) {
184+
const name = path.basename(filename);
185+
const dirHash = md5(path.dirname(filename));
186+
187+
return path.join(os.tmpdir(), `${dirHash}-${name}.lock`);
188+
}
189+
190+
/**
191+
* Create a lockfile (async)
192+
*
193+
* @param {string} filename
194+
*/
195+
export async function lock(filename: string) {
196+
await new Promise<void>((resolve, reject) => {
197+
lockfile.lock(
198+
getLockFilename(filename),
199+
{ wait: 6000, retryWait: 100, stale: 5000, retries: 100 },
200+
err => {
201+
if (err) {
202+
reject(err);
203+
return;
204+
}
205+
resolve();
206+
}
207+
);
208+
});
209+
}
210+
211+
/**
212+
* Remove a lockfile (async)
213+
*
214+
* @param {string} filename
215+
*/
216+
export async function unlock(filename: string) {
217+
await lfUnlock(getLockFilename(filename));
218+
}

0 commit comments

Comments
 (0)