UNPKG

4.22 kBJavaScriptView Raw
1/**
2 * External dependencies
3 */
4const fs = require( 'fs' );
5
6/**
7 * Internal dependencies
8 */
9const isBlockMetadataExperimental = require( './src/utils/is-block-metadata-experimental' );
10
11/**
12 * Creates a babel plugin that replaces experimental block imports with
13 * null variable declarations.
14 *
15 * For example:
16 * import * as experimentalBlock from "./experimental-block";
17 * On build becomes:
18 * const experimentalBlock = null;
19 *
20 * This ensures the dead code elimination removes the experimental blocks modules
21 * during the production build.
22 *
23 * For more context, see https://github.com/WordPress/gutenberg/pull/40655/
24 *
25 * @param {Function} shouldProcessImport Optional callback to decide whether a given import should be processed.
26 * @param {boolean} isGutenbergPlugin Whether to run the plugin.
27 */
28function createBabelPlugin( shouldProcessImport, isGutenbergPlugin ) {
29 if ( ! shouldProcessImport ) {
30 shouldProcessImport = isImportDeclarationAnExperimentalBlock;
31 }
32 if ( isGutenbergPlugin === undefined ) {
33 // process.env.npm_package_config_IS_GUTENBERG_PLUGIN is a string, not a boolean
34 isGutenbergPlugin =
35 String( process.env.npm_package_config_IS_GUTENBERG_PLUGIN ) ===
36 'true';
37 }
38 /**
39 * The babel plugin created by createBabelPlugin.
40 *
41 * @see createBabelPlugin.
42 * @param {import('@babel/core')} babel Current Babel object.
43 * @return {import('@babel/core').PluginObj} Babel plugin object.
44 */
45 return function babelPlugin( { types: t } ) {
46 if ( isGutenbergPlugin ) {
47 return {};
48 }
49
50 return {
51 visitor: {
52 ImportDeclaration( path ) {
53 // Only process the experimental blocks.
54 if ( ! shouldProcessImport( path ) ) {
55 return;
56 }
57
58 // Get the imported variable name.
59 const namespaceSpecifier = path.node.specifiers.find(
60 ( specifier ) =>
61 specifier.type === 'ImportNamespaceSpecifier'
62 );
63 const { name } = namespaceSpecifier.local;
64
65 path.replaceWith(
66 t.variableDeclaration( 'const', [
67 t.variableDeclarator(
68 t.identifier( name ),
69 t.nullLiteral()
70 ),
71 ] )
72 );
73 },
74 },
75 };
76 };
77}
78
79/**
80 * Tests whether an import declaration refers to an experimental block.
81 * In broad strokes, it's a block that says "__experimental" in its block.json file.
82 * For details, check the implementation.
83 *
84 * @param {Object} path Babel.js AST path representing the import declaration,
85 * @return {boolean} Whether the import represents an experimental block.
86 */
87function isImportDeclarationAnExperimentalBlock( path ) {
88 // Only look for wildcard imports like import * as a from "source":
89 const { node } = path;
90 const namespaceSpecifier = node.specifiers.find(
91 ( specifier ) => specifier.type === 'ImportNamespaceSpecifier'
92 );
93 if ( ! namespaceSpecifier || ! namespaceSpecifier.local ) {
94 return;
95 }
96
97 // Only look for imports starting with ./ and without additional slashes in the path.
98 const importedPath = node.source.value;
99 if (
100 ! importedPath ||
101 ! importedPath.startsWith( './' ) ||
102 importedPath.split( '/' ).length > 2
103 ) {
104 return false;
105 }
106
107 // Check the imported directory has a related block.json file.
108 const blockJsonPath = __dirname + '/src/' + importedPath + '/block.json';
109 if ( ! fs.existsSync( blockJsonPath ) ) {
110 return false;
111 }
112
113 // Read the block.json file related to this block
114 const { name } = namespaceSpecifier.local;
115 let blockJSONBuffer;
116 try {
117 blockJSONBuffer = fs.readFileSync( blockJsonPath );
118 } catch ( e ) {
119 process.stderr.write(
120 'Could not read block.json for the module "' +
121 importedPath +
122 '" imported under name "' +
123 name +
124 '" from path "' +
125 blockJsonPath +
126 '"'
127 );
128 throw e;
129 }
130 let blockJSON;
131 try {
132 blockJSON = JSON.parse( blockJSONBuffer );
133 } catch ( e ) {
134 process.stderr.write(
135 'Could not parse block.json for the module "' +
136 importedPath +
137 '" imported under name "' +
138 name +
139 '" read from path "' +
140 blockJsonPath +
141 '"'
142 );
143 throw e;
144 }
145 if ( ! isBlockMetadataExperimental( blockJSON ) ) {
146 return false;
147 }
148
149 return true;
150}
151
152const babelPlugin = createBabelPlugin();
153babelPlugin.createBabelPlugin = createBabelPlugin;
154module.exports = babelPlugin;