block_editor-1.0.x-dev/tools/webpack/generate-libraries.js
tools/webpack/generate-libraries.js
/**
* External dependencies
*/
const fs = require( 'fs' );
const path = require( 'path' );
const yaml = require( 'js-yaml' );
/**
* Webpack plugin to generate Drupal library definitions from built assets.
*/
class GenerateDrupalLibrariesPlugin {
constructor( options = {} ) {
this.options = {
buildDir: options.buildDir || 'build',
outputFile: options.outputFile || 'block_editor.libraries.yml',
...options,
};
}
apply( compiler ) {
compiler.hooks.afterEmit.tapAsync(
'GenerateDrupalLibrariesPlugin',
( compilation, callback ) => {
const buildPath = path.resolve( compiler.context, this.options.buildDir );
const outputPath = path.resolve( compiler.context, this.options.outputFile );
try {
const libraries = this.generateLibraries( buildPath );
this.writeLibrariesFile( outputPath, libraries );
console.log( '\n✓ Generated block_editor.libraries.yml\n' );
} catch ( error ) {
console.error( '\n✗ Error generating libraries.yml:', error.message, '\n' );
}
callback();
}
);
}
/**
* Generate library definitions from build directory.
*
* @param {string} buildPath Path to the build directory.
* @return {Object} Library definitions.
*/
generateLibraries( buildPath ) {
const libraries = {
'# Auto-generated libraries': null,
};
// Vendor libraries (React, ReactDOM, etc.)
libraries[ '# Vendor libraries' ] = null;
const vendorsPath = path.join( buildPath, 'vendors' );
if ( fs.existsSync( vendorsPath ) ) {
// React
if ( fs.existsSync( path.join( vendorsPath, 'react.min.js' ) ) ) {
libraries.react = {
js: {
'build/vendors/react.min.js': { minified: true },
},
};
}
// React DOM
if ( fs.existsSync( path.join( vendorsPath, 'react-dom.min.js' ) ) ) {
libraries.react_dom = {
js: {
'build/vendors/react-dom.min.js': { minified: true },
},
dependencies: [ 'block_editor/react' ],
};
}
// React JSX Runtime
if ( fs.existsSync( path.join( vendorsPath, 'react-jsx-runtime.min.js' ) ) ) {
libraries.react_jsx_runtime = {
js: {
'build/vendors/react-jsx-runtime.min.js': { minified: true },
},
dependencies: [ 'block_editor/react' ],
};
}
}
// WordPress packages
libraries[ '# WordPress packages' ] = null;
const wordpressPath = path.join( buildPath, 'wordpress' );
if ( fs.existsSync( wordpressPath ) ) {
const packages = fs.readdirSync( wordpressPath );
packages.forEach( ( packageName ) => {
const packagePath = path.join( wordpressPath, packageName );
const assetFile = path.join( packagePath, 'index.min.asset.php' );
if ( fs.existsSync( assetFile ) ) {
const library = this.createLibraryFromAsset(
`wordpress_${ packageName.replace( /-/g, '_' ) }`,
`build/wordpress/${ packageName }`,
assetFile
);
libraries[ library.name ] = library.definition;
}
} );
}
// Scan editor directory
libraries[ '# Custom packages' ] = null;
const editorPath = path.join( buildPath, 'editor' );
if ( fs.existsSync( editorPath ) ) {
const assetFile = path.join( editorPath, 'index.min.asset.php' );
if ( fs.existsSync( assetFile ) ) {
const library = this.createLibraryFromAsset(
'editor',
'build/editor',
assetFile
);
libraries[ library.name ] = library.definition;
}
}
// Main block_editor library
libraries[ '# Main libraries' ] = null;
const blockEditorLib = {
js: {
'build/block_editor.js': {},
},
dependencies: [ 'core/drupal', 'block_editor/editor' ],
};
// Check for generated CSS file
const generatedCssFile = path.join( buildPath, 'block_editor.css' );
if ( fs.existsSync( generatedCssFile ) ) {
blockEditorLib.css = {
theme: {
'build/block_editor.css': {},
},
};
} else if ( fs.existsSync( path.join( process.cwd(), 'css', 'block-editor-form.css' ) ) ) {
// Fallback to old CSS file if exists
blockEditorLib.css = {
theme: {
'css/block-editor-form.css': {},
},
};
}
libraries.block_editor_form = blockEditorLib;
return libraries;
}
/**
* Create a library definition from an asset file.
*
* @param {string} libraryName Name of the library.
* @param {string} buildPath Path to the built files (relative to module root).
* @param {string} assetFile Path to the asset.php file.
* @return {Object} Library definition with name and definition.
*/
createLibraryFromAsset( libraryName, buildPath, assetFile ) {
// Read and evaluate the PHP asset file
const assetContent = fs.readFileSync( assetFile, 'utf8' );
const assetData = this.parseAssetFile( assetContent );
const definition = {
js: {
[ `${ buildPath }/index.min.js` ]: {},
},
};
// Add dependencies
if ( assetData.dependencies && assetData.dependencies.length > 0 ) {
const deps = assetData.dependencies
.map( ( dep ) => {
// Convert WordPress dependencies to Drupal library format
// e.g., "wp-element" -> "block_editor/wordpress_element"
if ( dep.startsWith( 'wp-' ) ) {
const pkgName = dep.replace( 'wp-', '' ).replace( /-/g, '_' );
const depLibraryName = `block_editor/wordpress_${ pkgName }`;
// Don't add self-reference
if ( depLibraryName === `block_editor/${ libraryName }` ) {
return null;
}
return depLibraryName;
}
// Convert react dependencies to vendor libraries
if ( dep === 'react' ) {
return 'block_editor/react';
}
if ( dep === 'react-dom' ) {
return 'block_editor/react_dom';
}
if ( dep === 'react-jsx-runtime' ) {
return 'block_editor/react_jsx_runtime';
}
return dep;
} )
.filter( ( dep ) => dep !== null ); // Remove null entries
if ( deps.length > 0 ) {
definition.dependencies = deps;
}
}
// Check for CSS files
const cssFile = path.join( path.dirname( assetFile ), 'style.min.css' );
if ( fs.existsSync( cssFile ) ) {
definition.css = {
theme: {
[ `${ buildPath }/style.min.css` ]: {},
},
};
}
return {
name: libraryName,
definition,
};
}
/**
* Parse a PHP asset file to extract dependencies.
*
* @param {string} content PHP file content.
* @return {Object} Parsed asset data.
*/
parseAssetFile( content ) {
const result = {
dependencies: [],
version: null,
};
// Extract dependencies array
const depsMatch = content.match( /'dependencies'\s*=>\s*array\s*\((.*?)\)/s );
if ( depsMatch ) {
const depsString = depsMatch[ 1 ];
const deps = depsString.match( /'([^']+)'/g );
if ( deps ) {
result.dependencies = deps.map( ( dep ) => dep.replace( /'/g, '' ) );
}
}
// Extract version
const versionMatch = content.match( /'version'\s*=>\s*'([^']+)'/ );
if ( versionMatch ) {
result.version = versionMatch[ 1 ];
}
return result;
}
/**
* Write libraries to YAML file.
*
* @param {string} outputPath Path to output file.
* @param {Object} libraries Library definitions.
*/
writeLibrariesFile( outputPath, libraries ) {
let content = '# Auto-generated by webpack build process\n';
content += '# DO NOT EDIT MANUALLY - Changes will be overwritten\n\n';
Object.entries( libraries ).forEach( ( [ name, definition ] ) => {
if ( definition === null ) {
// Comment line
content += `${ name }\n`;
} else {
const yamlSection = yaml.dump( { [ name ]: definition }, {
indent: 2,
lineWidth: -1,
} );
content += yamlSection;
}
} );
fs.writeFileSync( outputPath, content, 'utf8' );
}
}
module.exports = GenerateDrupalLibrariesPlugin;
