diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d42294e8..5d9580278 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,10 @@ "editor.formatOnSave": true }, "flow.useLSP": true, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "cSpell.words": [ + "Packio", + "uglifyjs", + "wpack" + ] } diff --git a/packages/scripts/PREFACE.md b/packages/scripts/PREFACE.md index 9db95b16a..82066aa47 100644 --- a/packages/scripts/PREFACE.md +++ b/packages/scripts/PREFACE.md @@ -5,25 +5,62 @@ - DO NOT RELY ON `process.env.NODE_ENV`. Rather set it automatically depending on cli commands. - `start` - Start browsersync sever with webpack middleware. - `build` - Compile files. - - `init` - Create a `wpackio.server.js` through asking some questions (only if the file is not present). + - `init` - Create a `wpackio.server.json` through asking some questions (only if the file is not present). - Also set `process.env.BABEL_ENV` so that `babel-loader` can play nice, (especially with the preset-react). -## Structure `wpackio.project.js` +## For the PHP Script + +We will need to feed it the outputPath to keep things fast and not have it load the project.js +file. + +```php +enqueue( 'entry_name' ); +$enqueue->enqueue( 'entry_name', 'chunk_name' ); +$enqueue->enqueue( 'entry_name', 'chunk_name', [ + 'other' => 'localization', +] ); +``` + +## Structure `wpackio.project.json` ```js module.exports = { + // Project Identity + type: 'plugin', // Plugin or theme + slug: 'wpack-io', // Plugin or Theme slug, basically the directory name under `wp-content/` + // Used to generate banners on top of compiled stuff + bannerConfig: { + name: 'WordPress WebPack Bundler', + author: 'Swashata Ghosh', + license: 'GPL-3.0', + link: 'https://wpack.io', + version: '1.0.0', + copyrightText: + 'This software is released under the GPL-3.0 License\nhttps://opensource.org/licenses/GPL-3.0', + credit: true, + }, // Files we need to compile, and where to put files: [ // If this has length === 1, then single compiler { + name: 'mobile', entry: { // stuff + vendor: 'vendor.js', + main: ['src/mobile.js'], }, filename: '[name].js', - path: path.resolve(__dirname, 'dist'), + // Extra webpack config to be passed directly + webpackConfig: undefined, }, // If has more length, then multi-compiler ], + // Output path relative to the context directory + // We need relative path here, else, we can not map to publicPath + outputPath: 'dist', // Project specific config // Needs react? hasReact: true, @@ -33,6 +70,8 @@ module.exports = { externals: { jquery: 'jQuery', }, + // Webpack Aliases + alias: undefined, // Show overlay on development errorOverlay: true, // Auto optimization by webpack @@ -40,8 +79,8 @@ module.exports = { // // Won't hurt because we use PHP to automate loading optimizeSplitChunks: true, - // Extra webpack config to be passed directly - webpackConfig: false, + // Usually PHP and other files to watch and reload when changed + watch: 'inc/**/*.php', }; ``` @@ -53,7 +92,7 @@ Possible `package.json` { "name": "@wpackio/scripts", "version": "0.0.0", - "description": "@wpackios/scripts is a single dependency for using WordPress webpack script.", + "description": "@wpackio/scripts is a single dependency for using WordPress webpack script.", "main": "index.js", "repository": "https://github.com/swashata/wp-webpack-script", "author": "Swashata Ghosh", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index d10e592a0..64de3cabd 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -22,6 +22,7 @@ "@babel/preset-typescript": "^7.1.0", "@types/browser-sync": "^0.0.42", "@types/node": "^10.11.3", + "@types/signale": "^1.2.0", "@types/webpack": "^4.4.13", "@types/webpack-dev-middleware": "^2.0.2", "@types/webpack-hot-middleware": "^2.16.4", @@ -37,6 +38,7 @@ "optimize-css-assets-webpack-plugin": "^5.0.1", "postcss-loader": "^3.0.0", "sass-loader": "^7.1.0", + "signale": "^1.3.0", "slugify": "^1.3.1", "style-loader": "^0.23.0", "uglifyjs-webpack-plugin": "^2.0.1", diff --git a/packages/scripts/src/bin/index.ts b/packages/scripts/src/bin/index.ts index 53aa4154d..b337eaaae 100644 --- a/packages/scripts/src/bin/index.ts +++ b/packages/scripts/src/bin/index.ts @@ -1,6 +1,11 @@ #!/usr/bin/env node import program from 'commander'; import path from 'path'; +import signale from 'signale'; +import { ProjectConfig } from '../config/project.config.default'; +import { ServerConfig } from '../config/server.config.default'; +import { Build } from '../scripts/Build'; +import { Server } from '../scripts/Server'; /* tslint:disable:no-require-imports no-var-requires */ const pkg = require('../../package.json'); @@ -43,16 +48,68 @@ program '-s, --server-config [path]', 'Path to server config. If it differs from ./wpackio.server.js' ) - .action(options => { - isValidCommand = true; - console.log('should start script.', options.context); - // Set process.env.NODE_ENV to development - // Set process.env.BABEL_ENV to development - // Get project and server config JSONs. - // Start the webpack/browserSync server - // Listen for SIGTERM and quit properly - // Listen for keyinput and invalidate webpack builds. - }); + .action( + ( + options: + | { + context?: string; + projectConfig?: string; + serverConfig?: string; + } + | undefined + ) => { + isValidCommand = true; + signale.start('Starting up wpack.io development server'); + // Set process.env.NODE_ENV to development + process.env.NODE_ENV = 'development'; + // Set process.env.BABEL_ENV to development + process.env.BABEL_ENV = 'development'; + // Get project and server config JSONs. + const cwd = resolveCWD(options); + signale.info(`Using startup path: ${cwd}`); + try { + const { + projectConfig, + serverConfig, + projectConfigPath, + serverConfigPath, + } = getProjectAndServerConfig(cwd, options); + signale.info(`Using project config from ${projectConfigPath}`); + signale.info(`Using server config from ${serverConfigPath}`); + // Start the webpack/browserSync server + const server: Server = new Server( + projectConfig, + serverConfig, + cwd + ); + server.serve(); + // Listen for SIGINT and quit properly + process.on('SIGINT', () => { + signale.complete('Gracefully ending development server'); + server.stop(); + signale.success( + 'To create production build, run `yarn build` or `npm run build`' + ); + signale.star('Thank you for using https://wpack.io.'); + signale.star('To spread the ❤️ please tweet.'); + process.exit(0); + }); + process.on('SIGKILL', () => { + server.stop(); + }); + process.on('SIGTERM', () => { + server.stop(); + }); + } catch (e) { + signale.error( + 'Could not start development server. Please check the log below.' + ); + signale.fatal(e); + process.exit(1); + } + // Listen for keyinput and invalidate webpack builds. + } + ); // Build the script program @@ -69,11 +126,48 @@ program ) .action(options => { isValidCommand = true; - console.log('should build the script', options.context); + signale.start('Creating production builds...'); // Set process.env.NODE_ENV to production + process.env.NODE_ENV = 'production'; // Set process.env.BABEL_ENV to production + process.env.BABEL_ENV = 'production'; // Get project and server config JSONs. - // Compile scripts using webpack + const cwd = resolveCWD(options); + signale.info(`Using startup path: ${cwd}`); + try { + const { + projectConfig, + serverConfig, + projectConfigPath, + serverConfigPath, + } = getProjectAndServerConfig(cwd, options); + signale.info(`Using project config from ${projectConfigPath}`); + signale.info(`Using server config from ${serverConfigPath}`); + // Start the webpack/browserSync server + const build: Build = new Build(projectConfig, serverConfig, cwd); + build + .build() + .then(log => { + signale.success( + 'Build Successful. Please check the log below' + ); + console.log(log); + process.exit(0); + }) + .catch(err => { + signale.fatal( + 'Could not create production build. Please check the log below' + ); + console.log(err); + process.exit(1); + }); + } catch (e) { + signale.error( + 'Could not start development server. Please check the log below.' + ); + signale.fatal(e); + process.exit(1); + } }); // Init @@ -81,9 +175,117 @@ program.parse(process.argv); // error on unknown commands if (!isValidCommand) { - console.error( - 'Invalid command: %s\nSee --help for a list of available commands.', + signale.error( + 'Invalid command: %s\nSee usage below.\n\n', program.args.join(' ') ); - process.exit(1); + program.help(); } + +/** + * Resolve `cwd`, a.k.a, current working directory or context from user input. + * It takes into account the `--context [path]` option from CLI and uses process + * cwd, if not provided. + * + * @param options Options as received from CLI + */ +function resolveCWD( + options: { context?: string | undefined } | undefined +): string { + let cwd = process.cwd(); + // If user has provided cwd, then use that instead + if (options && options.context) { + const { context } = options; + if (path.isAbsolute(options.context)) { + cwd = context; + } else { + cwd = path.resolve(cwd, context); + } + } + + return cwd; +} + +// tslint:disable: non-literal-require +function getProjectAndServerConfig( + cwd: string, + options: { projectConfig?: string; serverConfig?: string } | undefined +): { + projectConfig: ProjectConfig; + serverConfig: ServerConfig; + projectConfigPath: string; + serverConfigPath: string; +} { + // Get the config file paths from options + // If user is passing relative path, then it will be used along with cwd + // If it is absolute path, then the absolute would be used instead + // This is how path.resolve works. + const projectConfigPath = path.resolve( + cwd, + options && options.projectConfig + ? options.projectConfig + : 'wpackio.project.js' + ); + + const serverConfigPath = path.resolve( + cwd, + options && options.serverConfig + ? options.serverConfig + : 'wpackio.server.js' + ); + + // Now create the configuration objects + let projectConfig: ProjectConfig; + let serverConfig: ServerConfig; + + // First check to see if the files are present + try { + projectConfig = require(projectConfigPath) as ProjectConfig; + } catch (e) { + throw new Error( + `Could not find project configuration at:\n${projectConfigPath}\nPlease make sure the file exists or adjust your --context or --project-config parameters.` + ); + } + try { + serverConfig = require(serverConfigPath) as ServerConfig; + } catch (e) { + throw new Error( + `Could not find server configuration at:\n${serverConfigPath}\nPlease make sure the file exists or adjust your --context or --server-config parameters.` + ); + } + + // Now validate them + if (typeof projectConfig !== 'object') { + throw new Error( + `Project configuration must export an object literal. Right now it is ${typeof projectConfig}` + ); + } + if (typeof serverConfig !== 'object') { + throw new Error( + `Server configuration must export an object literal. Right now it is ${typeof serverConfig}` + ); + } + // @todo + // Also validate the config, but let's leave it for now + // Make sure to do it in future + + return { projectConfig, serverConfig, projectConfigPath, serverConfigPath }; +} + +// Error out on force close +process.on('SIGKILL', () => { + signale.fatal( + 'The operation failed because the process exited too early. ' + + 'This probably means the system ran out of memory or someone called ' + + '`kill -9` on the process.' + ); + process.exit(1); +}); +process.on('SIGTERM', () => { + signale.fatal( + 'The operation failed because the process exited too early. ' + + 'Someone might have called `kill` or `killall`, or the system could ' + + 'be shutting down.' + ); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index acc9d466a..4f495c58d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1244,6 +1244,12 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/signale@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/signale/-/signale-1.2.0.tgz#575719e586a1352cf1de04478b5fc0920e221000" + dependencies: + "@types/node" "*" + "@types/tapable@*": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" @@ -2404,7 +2410,7 @@ chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.0, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.0, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -7139,6 +7145,13 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +pkg-conf@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + pkg-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" @@ -8313,6 +8326,14 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +signale@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/signale/-/signale-1.3.0.tgz#1b4917c2c7a8691550adca0ad1da750a662b4497" + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + pkg-conf "^2.1.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"