diff --git a/.gitignore b/.gitignore index 13e6170afaa53..4e10215883008 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Directories/files that may be generated by this project build +build-module coverage /hooks node_modules diff --git a/bin/packages/build.js b/bin/packages/build.js new file mode 100755 index 0000000000000..b2b4ec9dded3c --- /dev/null +++ b/bin/packages/build.js @@ -0,0 +1,142 @@ +/** + * script to build WordPress packages into `build/` directory. + * + * Example: + * node ./scripts/build.js + */ + +/** + * External Dependenceis + */ +const fs = require( 'fs' ); +const path = require( 'path' ); +const glob = require( 'glob' ); +const babel = require( 'babel-core' ); +const chalk = require( 'chalk' ); +const mkdirp = require( 'mkdirp' ); + +/** + * Internal dependencies + */ +const getPackages = require( './get-packages' ); + +/** + * Module Constants + */ +const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); +const SRC_DIR = 'src'; +const BUILD_DIR = { + main: 'build', + module: 'build-module', +}; +const DONE = chalk.reset.inverse.bold.green( ' DONE ' ); + +/** + * Babel Configuration + */ +const babelDefaultConfig = require( '@wordpress/babel-preset-default' ); +babelDefaultConfig.babelrc = false; +const presetEnvConfig = babelDefaultConfig.presets[ 0 ][ 1 ]; +const babelConfigs = { + main: Object.assign( + {}, + babelDefaultConfig, + { presets: [ + [ 'env', Object.assign( + {}, + presetEnvConfig, + { modules: 'commonjs' }, + ) ], + ] } + ), + module: babelDefaultConfig, +}; + +/** + * Get the package name for a specified file + * + * @param {string} file File name + * @return {string} Package name + */ +function getPackageName( file ) { + return path.relative( PACKAGES_DIR, file ).split( path.sep )[ 0 ]; +} + +/** + * Get Build Path for a specified file + * + * @param {string} file File to build + * @param {string} buildFolder Output folder + * @return {string} Build path + */ +function getBuildPath( file, buildFolder ) { + const pkgName = getPackageName( file ); + const pkgSrcPath = path.resolve( PACKAGES_DIR, pkgName, SRC_DIR ); + const pkgBuildPath = path.resolve( PACKAGES_DIR, pkgName, buildFolder ); + const relativeToSrcPath = path.relative( pkgSrcPath, file ); + return path.resolve( pkgBuildPath, relativeToSrcPath ); +} + +/** + * Build a file for the required environments (node and ES5) + * + * @param {string} file File path to build + * @param {boolean} silent Show logs + */ +function buildFile( file, silent ) { + buildFileFor( file, silent, 'main' ); + buildFileFor( file, silent, 'module' ); +} + +/** + * Build a file for a specific environment + * + * @param {string} file File path to build + * @param {boolean} silent Show logs + * @param {string} environment Dist environment (node or es5) + */ +function buildFileFor( file, silent, environment ) { + const buildDir = BUILD_DIR[ environment ]; + const destPath = getBuildPath( file, buildDir ); + const babelOptions = babelConfigs[ environment ]; + + mkdirp.sync( path.dirname( destPath ) ); + const transformed = babel.transformFileSync( file, babelOptions ).code; + fs.writeFileSync( destPath, transformed ); + if ( ! silent ) { + process.stdout.write( + chalk.green( ' \u2022 ' ) + + path.relative( PACKAGES_DIR, file ) + + chalk.green( ' \u21D2 ' ) + + path.relative( PACKAGES_DIR, destPath ) + + '\n' + ); + } +} + +/** + * Build the provided package path + * + * @param {string} packagePath absolute package path + */ +function buildPackage( packagePath ) { + const srcDir = path.resolve( packagePath, SRC_DIR ); + const files = glob.sync( srcDir + '/**/*.js', { nodir: true } ) + .filter( ( file ) => ! /\.test\.js/.test( file ) ); + + process.stdout.write( `${ path.basename( packagePath ) }\n` ); + + files.forEach( ( file ) => buildFile( file, true ) ); + process.stdout.write( `${ DONE }\n` ); +} + +const files = process.argv.slice( 2 ); + +if ( files.length ) { + files.forEach( buildFile ); +} else { + process.stdout.write( chalk.inverse( '>> Building packages \n' ) ); + getPackages() + .forEach( buildPackage ); + process.stdout.write( '\n' ); +} diff --git a/bin/packages/get-packages.js b/bin/packages/get-packages.js new file mode 100644 index 0000000000000..2f2ede44273b7 --- /dev/null +++ b/bin/packages/get-packages.js @@ -0,0 +1,24 @@ +/** + * External Dependenceis + */ +const fs = require( 'fs' ); +const path = require( 'path' ); + +/** + * Module Constants + */ +const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); + +/** + * Returns the absolute path of all WordPress packages + * + * @return {Array} Package paths + */ +function getPackages() { + return fs + .readdirSync( PACKAGES_DIR ) + .map( ( file ) => path.resolve( PACKAGES_DIR, file ) ) + .filter( ( f ) => fs.lstatSync( path.resolve( f ) ).isDirectory() ); +} + +module.exports = getPackages; diff --git a/bin/packages/watch.js b/bin/packages/watch.js new file mode 100644 index 0000000000000..fbd7f5924b0da --- /dev/null +++ b/bin/packages/watch.js @@ -0,0 +1,66 @@ +/** + * External dependencies + */ +const fs = require( 'fs' ); +const { execSync } = require( 'child_process' ); +const path = require( 'path' ); +const chalk = require( 'chalk' ); + +/** + * Internal dependencies + */ +const getPackages = require( './get-packages' ); + +const BUILD_CMD = `node ${ path.resolve( __dirname, './build.js' ) }`; + +let filesToBuild = new Map(); + +const exists = ( filename ) => { + try { + return fs.statSync( filename ).isFile(); + } catch ( e ) {} + return false; +}; +const rebuild = ( filename ) => filesToBuild.set( filename, true ); + +getPackages().forEach( ( p ) => { + const srcDir = path.resolve( p, 'src' ); + try { + fs.accessSync( srcDir, fs.F_OK ); + fs.watch( path.resolve( p, 'src' ), { recursive: true }, ( event, filename ) => { + const filePath = path.resolve( srcDir, filename ); + + if ( ( event === 'change' || event === 'rename' ) && exists( filePath ) ) { + // eslint-disable-next-line no-console + console.log( chalk.green( '->' ), `${ event }: ${ filename }` ); + rebuild( filePath ); + } else { + const buildFile = path.resolve( srcDir, '..', 'build', filename ); + try { + fs.unlinkSync( buildFile ); + process.stdout.write( + chalk.red( ' \u2022 ' ) + + path.relative( path.resolve( srcDir, '..', '..' ), buildFile ) + + ' (deleted)' + + '\n' + ); + } catch ( e ) {} + } + } ); + } catch ( e ) { + // doesn't exist + } +} ); + +setInterval( () => { + const files = Array.from( filesToBuild.keys() ); + if ( files.length ) { + filesToBuild = new Map(); + try { + execSync( `${ BUILD_CMD } ${ files.join( ' ' ) }`, { stdio: [ 0, 1, 2 ] } ); + } catch ( e ) {} + } +}, 100 ); + +// eslint-disable-next-line no-console +console.log( chalk.red( '->' ), chalk.cyan( 'Watching for changes...' ) ); diff --git a/package.json b/package.json index 9f2ff5eda4a00..8413f6b352076 100644 --- a/package.json +++ b/package.json @@ -121,12 +121,14 @@ }, "scripts": { "prebuild": "check-node-version --package", - "build": "cross-env NODE_ENV=production webpack", + "build:packages": "rimraf ./packages/*/build ./packages/*/build-module && node ./bin/packages/build.js", + "build": "npm run build:packages && cross-env NODE_ENV=production webpack", "lint": "eslint .", "lint:fix": "eslint . --fix", "lint-php": "docker-compose run --rm composer run-script lint", "predev": "check-node-version --package", - "dev": "cross-env webpack --watch", + "dev": "npm run build:packages && concurrently \"cross-env webpack --watch\" \"npm run dev:packages\"", + "dev:packages": "node ./bin/packages/watch.js", "test": "npm run lint && npm run test-unit", "test-php": "npm run lint-php && npm run test-unit-php", "ci": "concurrently \"npm run lint && npm run build\" \"npm run test-unit:coverage-ci\"", diff --git a/packages/date/package.json b/packages/date/package.json index 47dad64738354..a43024f1503fb 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -16,7 +16,8 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, - "main": "src/index.js", + "main": "build/index.js", + "module": "build-module/index.js", "dependencies": { "moment": "2.22.1", "moment-timezone": "0.5.16"