From fe61b4899f64809a527b7c5ed50c0179c05d83f3 Mon Sep 17 00:00:00 2001 From: Swashata Ghosh Date: Thu, 11 Oct 2018 17:15:47 +0530 Subject: [PATCH] feat: feature freeze on wpackio/scripts No more featured needed for v1. Let's focus on finishing up. --- examples/plugin/.vscode/settings.json | 3 + examples/plugin/package.json | 5 ++ examples/plugin/src/app/index.css | 3 +- examples/plugin/src/app/mobile.js | 4 +- examples/plugin/src/app/modules/dynamic.js | 2 +- examples/plugin/src/app/modules/logger.js | 3 +- examples/plugin/src/reactapp/App.jsx | 15 ++++ .../plugin/src/reactapp/components/Box.jsx | 10 +++ .../plugin/src/reactapp/components/List.jsx | 11 +++ .../plugin/src/reactapp/components/Todo.jsx | 37 ++++++++++ examples/plugin/src/reactapp/index.jsx | 8 ++ examples/plugin/wpackio-plugin.php | 43 +++++++++-- examples/plugin/wpackio.project.js | 12 +++ .../config/WebpackConfigHelper.spec.ts | 44 ++++++++++- .../scripts/src/config/WebpackConfigHelper.ts | 13 +++- .../src/config/project.config.default.ts | 19 ++++- yarn.lock | 74 ++++++++++++++++++- 17 files changed, 284 insertions(+), 22 deletions(-) create mode 100644 examples/plugin/.vscode/settings.json create mode 100644 examples/plugin/src/reactapp/App.jsx create mode 100644 examples/plugin/src/reactapp/components/Box.jsx create mode 100644 examples/plugin/src/reactapp/components/List.jsx create mode 100644 examples/plugin/src/reactapp/components/Todo.jsx create mode 100644 examples/plugin/src/reactapp/index.jsx diff --git a/examples/plugin/.vscode/settings.json b/examples/plugin/.vscode/settings.json new file mode 100644 index 000000000..a83500027 --- /dev/null +++ b/examples/plugin/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "eslint.autoFixOnSave": true +} \ No newline at end of file diff --git a/examples/plugin/package.json b/examples/plugin/package.json index 8cae7bb67..bb1a4b06a 100644 --- a/examples/plugin/package.json +++ b/examples/plugin/package.json @@ -14,5 +14,10 @@ "scripts": { "exstart": "wpackio-scripts start", "exbuild": "wpackio-scripts build" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-hot-loader": "^4.3.11" } } diff --git a/examples/plugin/src/app/index.css b/examples/plugin/src/app/index.css index 98ca2057d..8e774627e 100644 --- a/examples/plugin/src/app/index.css +++ b/examples/plugin/src/app/index.css @@ -2,7 +2,8 @@ body { background-color: black !important; } +/* CSS HMR */ .site-title a { - color: slategray !important; + color: azure !important; background-color: aqua !important; } diff --git a/examples/plugin/src/app/mobile.js b/examples/plugin/src/app/mobile.js index b67d9e3d3..baf01e1a2 100644 --- a/examples/plugin/src/app/mobile.js +++ b/examples/plugin/src/app/mobile.js @@ -1 +1,3 @@ -console.log('Hello Mobile!!'); +// Auto page load when HMR is not defined + +console.log('Hello Mobile!'); diff --git a/examples/plugin/src/app/modules/dynamic.js b/examples/plugin/src/app/modules/dynamic.js index 3b2c79085..9a5afdc77 100644 --- a/examples/plugin/src/app/modules/dynamic.js +++ b/examples/plugin/src/app/modules/dynamic.js @@ -1,3 +1,3 @@ export default function iAmGroot() { - console.log('I am dynamic groot! With Hot reloading!😱💩'); + console.log('I am dynamic groot! With Hot reloading!😱🐶'); } diff --git a/examples/plugin/src/app/modules/logger.js b/examples/plugin/src/app/modules/logger.js index b3d462cfb..d91b63072 100644 --- a/examples/plugin/src/app/modules/logger.js +++ b/examples/plugin/src/app/modules/logger.js @@ -1,3 +1,4 @@ export default function logger() { - console.log('Load me 🤟🎉💥😜'); + // Automatic Hot Module Replacement + console.log('Load me 🤟🎉💥😜😺'); } diff --git a/examples/plugin/src/reactapp/App.jsx b/examples/plugin/src/reactapp/App.jsx new file mode 100644 index 000000000..88d10d30b --- /dev/null +++ b/examples/plugin/src/reactapp/App.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { hot } from 'react-hot-loader'; +import Box from './components/Box'; +import Todo from './components/Todo'; + +const App = () => ( + +

I can be hot reloaded!

+

Heres something of an app.

+

Todo App.!

+ +
+); + +export default hot(module)(App); diff --git a/examples/plugin/src/reactapp/components/Box.jsx b/examples/plugin/src/reactapp/components/Box.jsx new file mode 100644 index 000000000..1ab0aa09c --- /dev/null +++ b/examples/plugin/src/reactapp/components/Box.jsx @@ -0,0 +1,10 @@ +import React from 'react'; + +const Box = ({ heading, children }) => ( +
+

{heading}

+
{children}
+
+); + +export default Box; diff --git a/examples/plugin/src/reactapp/components/List.jsx b/examples/plugin/src/reactapp/components/List.jsx new file mode 100644 index 000000000..836cb26f4 --- /dev/null +++ b/examples/plugin/src/reactapp/components/List.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const List = ({ items }) => ( + +); + +export default List; diff --git a/examples/plugin/src/reactapp/components/Todo.jsx b/examples/plugin/src/reactapp/components/Todo.jsx new file mode 100644 index 000000000..f5c942a27 --- /dev/null +++ b/examples/plugin/src/reactapp/components/Todo.jsx @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; +import List from './List'; + +export default class Todo extends Component { + constructor(props) { + super(props); + this.state = { + term: '', + items: [], + }; + } + + onChange = event => { + this.setState({ term: event.target.value }); + }; + + onSubmit = event => { + event.preventDefault(); + this.setState(state => ({ + term: '', + items: [...state.items, state.term], + })); + }; + + render() { + const { term, items } = this.state; + return ( +
+
+ + +
+ +
+ ); + } +} diff --git a/examples/plugin/src/reactapp/index.jsx b/examples/plugin/src/reactapp/index.jsx new file mode 100644 index 000000000..a074e2ab9 --- /dev/null +++ b/examples/plugin/src/reactapp/index.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { render } from 'react-dom'; +import App from './App'; + +document.addEventListener('DOMContentLoaded', () => { + const entry = document.querySelector('#wpackio-reactapp'); + render(, entry); +}); diff --git a/examples/plugin/wpackio-plugin.php b/examples/plugin/wpackio-plugin.php index 8e51c9c5c..4a1c3ef4c 100644 --- a/examples/plugin/wpackio-plugin.php +++ b/examples/plugin/wpackio-plugin.php @@ -19,14 +19,41 @@ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt */ -// Create an admin page -add_action( 'wp_enqueue_scripts', 'wpackio_plugin_enqueue' ); +// Get our dependency require_once dirname( __FILE__ ) . '/inc/Enqueue.php'; -$enqueue = new \WPackio\Enqueue( 'wpackplugin', 'dist', '1.0.0', 'plugin', __FILE__ ); -function wpackio_plugin_enqueue() { - global $enqueue; - $enqueue->enqueue( 'app', 'main', [] ); - $enqueue->enqueue( 'app', 'mobile', [] ); - $enqueue->enqueue( 'foo', 'main', [] ); +// Do stuff through this plugin +class WPackioPluginInit { + /** + * @var \WPackio\Enqueue + */ + public $enqueue; + + public function __construct() { + // It is important that we init the Enqueue class right at the plugin/theme load time + $this->enqueue = new \WPackio\Enqueue( 'wpackplugin', 'dist', '1.0.0', 'plugin', __FILE__ ); + // Enqueue a few of our entry points + add_action( 'wp_enqueue_scripts', [ $this, 'plugin_enqueue' ] ); + // And heres a react app with shortcode + add_shortcode( 'wpackio-reactapp', [ $this, 'reactapp' ] ); + } + + + function plugin_enqueue() { + $this->enqueue->enqueue( 'app', 'main', [] ); + $this->enqueue->enqueue( 'app', 'mobile', [] ); + $this->enqueue->enqueue( 'foo', 'main', [] ); + } + + function reactapp( $atts, $content = null ) { + // Enqueue our react app scripts + $this->enqueue->enqueue( 'reactapp', 'main', [] ); + + // Print the entry point + return '
'; + } } + + +// Init +new WPackioPluginInit(); diff --git a/examples/plugin/wpackio.project.js b/examples/plugin/wpackio.project.js index c5ee20599..43f897e61 100644 --- a/examples/plugin/wpackio.project.js +++ b/examples/plugin/wpackio.project.js @@ -35,6 +35,13 @@ module.exports = { // Extra webpack config to be passed directly webpackConfig: undefined, }, + // Another app just for showing react + { + name: 'reactapp', + entry: { + main: ['./src/reactapp/index.jsx'], + }, + }, ], // Output path relative to the context directory // We need relative path here, else, we can not map to publicPath @@ -59,4 +66,9 @@ module.exports = { optimizeSplitChunks: true, // Usually PHP and other files to watch and reload when changed watch: 'inc/**/*.php', + // Hook into babeloverride so that we can add react-hot-loader plugin + jsBabelOverride: defaults => ({ + ...defaults, + plugins: ['react-hot-loader/babel'], + }), }; diff --git a/packages/scripts/__tests__/config/WebpackConfigHelper.spec.ts b/packages/scripts/__tests__/config/WebpackConfigHelper.spec.ts index 67fafb63c..8aea39709 100644 --- a/packages/scripts/__tests__/config/WebpackConfigHelper.spec.ts +++ b/packages/scripts/__tests__/config/WebpackConfigHelper.spec.ts @@ -4,6 +4,7 @@ import webpack from 'webpack'; import { ProjectConfig, projectConfigDefault, + webpackOptionsOverrideFunction, } from '../../src/config/project.config.default'; import { ServerConfig, @@ -314,7 +315,7 @@ describe('CreateWebPackConfig', () => { } }); - test('overrides all babel-loader options from config', () => { + test('overrides all babel-loader options from config object', () => { const override: webpack.RuleSetLoader['options'] = { presets: 'foo', plugins: ['bar', 'baz'], @@ -347,6 +348,47 @@ describe('CreateWebPackConfig', () => { throw new Error('Module is not an array'); } }); + + test('overrides all babel-loader options from config function', () => { + const override: webpackOptionsOverrideFunction = defaults => { + if (typeof defaults === 'string') { + return defaults; + } + return { + ...defaults, + plugins: ['react-hot-loader/babel'], + }; + }; + const cwc = new WebpackConfigHelper( + projectConfig.files[0], + { + ...getConfigFromProjectAndServer( + projectConfig, + serverConfig + ), + jsBabelOverride: override, + tsBabelOverride: override, + }, + '/foo/bar', + true + ); + const modules = cwc.getModule(); + if (Array.isArray(modules.rules)) { + const jsTsRules = findWpackIoBabelOnTJs(modules); + expect(jsTsRules).toHaveLength(2); + jsTsRules.forEach(rule => { + if (rule && rule.use && rule.use[0].options) { + expect(rule.use[0].options).toMatchObject({ + plugins: ['react-hot-loader/babel'], + }); + } else { + throw new Error('JavaScript rule is undefined'); + } + }); + } else { + throw new Error('Module is not an array'); + } + }); }); test('uses style loader when in dev mode', () => { diff --git a/packages/scripts/src/config/WebpackConfigHelper.ts b/packages/scripts/src/config/WebpackConfigHelper.ts index 314d2592c..5fa9551d5 100644 --- a/packages/scripts/src/config/WebpackConfigHelper.ts +++ b/packages/scripts/src/config/WebpackConfigHelper.ts @@ -12,6 +12,8 @@ import { BannerConfig, FileConfig, ProjectConfig, + webpackLoaderOptionsOverride, + webpackOptionsOverrideFunction, } from './project.config.default'; import { ServerConfig } from './server.config.default'; @@ -455,11 +457,18 @@ ${bannerConfig.copyrightText}${bannerConfig.credit ? creditNote : ''}`, */ private getOverrideWebpackRuleOptions( defaults: webpack.RuleSetLoader['options'], - override: webpack.RuleSetLoader['options'] | undefined + override: webpackLoaderOptionsOverride ): webpack.RuleSetLoader['options'] { // If override is not undefined or null, then return it if (override != null) { - return override; + // If it is a function + if (typeof override === 'function') { + return (override as webpackOptionsOverrideFunction)( + defaults || {} + ) as webpack.RuleSetLoader['options']; + } else { + return override; + } } // Otherwise just return default return defaults; diff --git a/packages/scripts/src/config/project.config.default.ts b/packages/scripts/src/config/project.config.default.ts index 248b9ac05..bcd67b316 100644 --- a/packages/scripts/src/config/project.config.default.ts +++ b/packages/scripts/src/config/project.config.default.ts @@ -33,6 +33,19 @@ export interface FileConfig { webpackConfig?: webpack.Configuration; } +export type webpackOptionsOverrideFunction = ( + // tslint:disable-next-line:no-any + defaults: string | { [x: string]: any } +) => // tslint:disable-next-line:no-any +string | { [x: string]: any }; + +export type webpackLoaderOptionsOverride = + | webpackOptionsOverrideFunction + // tslint:disable-next-line:no-any + | { [x: string]: any } + | string + | undefined; + /** * Main Project Config shape under `wpackio.project.js` file. */ @@ -61,11 +74,9 @@ export interface ProjectConfig { // If provided it is spread over whatever wpackio/scripts generates tsBabelPresetOptions?: PresetOptions; // Completely overrides `babel-loader` options for javascript files - // tslint:disable-next-line:no-any - jsBabelOverride?: string | { [x: string]: any }; + jsBabelOverride?: webpackLoaderOptionsOverride; // Completely overrides `babel-loader` options for typescript files - // tslint:disable-next-line:no-any - tsBabelOverride?: string | { [x: string]: any }; + tsBabelOverride?: webpackLoaderOptionsOverride; externals?: webpack.Configuration['externals']; alias?: webpack.Resolve['alias']; errorOverlay?: boolean; diff --git a/yarn.lock b/yarn.lock index ad2f6910a..cbb77c726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3505,6 +3505,10 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" +dom-walk@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" + domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -4175,7 +4179,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" -fast-levenshtein@~2.0.4: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -4635,6 +4639,13 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +global@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" + dependencies: + min-document "^2.19.0" + process "~0.5.1" + globals@^11.1.0, globals@^11.7.0: version "11.7.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673" @@ -4848,6 +4859,10 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -6238,7 +6253,7 @@ log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -loose-envify@^1.0.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: @@ -6478,6 +6493,12 @@ mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + dependencies: + dom-walk "^0.1.0" + mini-css-extract-plugin@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.3.tgz#98d60fcc5d228c3e36a9bd15a1d6816d6580beb8" @@ -7858,6 +7879,10 @@ process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" +process@~0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" + progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" @@ -7886,7 +7911,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.6.2: +prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: @@ -8057,10 +8082,43 @@ react-dev-utils@^6.0.4: strip-ansi "4.0.0" text-table "0.2.0" +react-dom@^16.5.2: + version "16.5.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + schedule "^0.5.0" + react-error-overlay@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.0.4.tgz#39cf184d770f98b65a2ee59a779d03b8d092b69e" +react-hot-loader@^4.3.11: + version "4.3.11" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.3.11.tgz#fe5cf7be7700c249b58293f977c1e6e0900f0d87" + dependencies: + fast-levenshtein "^2.0.6" + global "^4.3.0" + hoist-non-react-statics "^2.5.0" + prop-types "^15.6.1" + react-lifecycles-compat "^3.0.4" + shallowequal "^1.0.2" + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + +react@^16.5.2: + version "16.5.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + schedule "^0.5.0" + read-cmd-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" @@ -8581,6 +8639,12 @@ sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" +schedule@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.5.0.tgz#c128fffa0b402488b08b55ae74bb9df55cc29cc8" + dependencies: + object-assign "^4.1.1" + schema-utils@^0.4.4, schema-utils@^0.4.5: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" @@ -8716,6 +8780,10 @@ shallow-clone@^1.0.0: kind-of "^5.0.0" mixin-object "^2.0.1" +shallowequal@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"