├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── .npmrc ├── LICENSE ├── README.md ├── _build.js ├── _start.js ├── package.json ├── src ├── functions.php ├── index.php ├── js │ ├── main.js │ └── module │ │ └── title.js └── sass │ ├── _elements.scss │ └── style.scss └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | "transform-async-to-generator" 7 | ] 8 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | 6 | max_line_length = 120 7 | indent_style = space 8 | indent_size = 2 9 | quote_type = single 10 | 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | 19 | [*.{scss,css}] 20 | indent_size = 4 21 | 22 | [*.{html,php}] 23 | indent_size = 3 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "airbnb", 4 | "parser": "babel-eslint", 5 | "globals": { 6 | }, 7 | "env": { 8 | "browser": true, 9 | "node": true 10 | }, 11 | "rules": { 12 | "brace-style": [ 13 | 2, 14 | "stroustrup", 15 | { 16 | "allowSingleLine": true 17 | } 18 | ], 19 | "indent": [ 20 | 2, 21 | 2, 22 | { 23 | "SwitchCase": 1 24 | } 25 | ], 26 | "id-length": [ 27 | 2, 28 | { 29 | "min": 1 30 | } 31 | ], 32 | "new-cap": 0, 33 | "max-len": [ 34 | 2, 35 | 120 36 | ], 37 | "no-use-before-define": [ 38 | 2, 39 | "nofunc" 40 | ], 41 | "spaced-comment": 0, 42 | "no-unused-vars": 1, 43 | "semi": 1, 44 | "array-bracket-spacing": [ 45 | 2, 46 | "always" 47 | ], 48 | "computed-property-spacing": [ 49 | 2, 50 | "always" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .jshintrc text 60 | .jscsrc text 61 | .jshintignore text 62 | .csslintrc text 63 | 64 | # misc config 65 | *.yaml text 66 | *.yml text 67 | .editorconfig text 68 | 69 | # build config 70 | *.npmignore text 71 | *.bowerrc text 72 | 73 | # Heroku 74 | Procfile text 75 | .slugignore text 76 | 77 | # Documentation 78 | *.md text 79 | LICENSE text 80 | AUTHORS text 81 | 82 | 83 | # 84 | ## These files are binary and should be left untouched 85 | # 86 | 87 | # (binary is a macro for -text -diff) 88 | *.png binary 89 | *.jpg binary 90 | *.jpeg binary 91 | *.gif binary 92 | *.ico binary 93 | *.mov binary 94 | *.mp4 binary 95 | *.mp3 binary 96 | *.flv binary 97 | *.fla binary 98 | *.swf binary 99 | *.gz binary 100 | *.zip binary 101 | *.7z binary 102 | *.ttf binary 103 | *.eot binary 104 | *.woff binary 105 | *.pyc binary 106 | *.pdf binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | _msp 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Manu Phatak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-hmr-wordpress 2 | Simple example of webpack + hmr + browsersync + php (wordpress) 3 | -------------------------------------------------------------------------------- /_build.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0, consistent-return:0 */ 2 | const path = require('path'); 3 | const fs = require('fs-promise'); 4 | const webpack = require('webpack'); 5 | const glob = require('glob'); 6 | 7 | const webpackConfig = require('./webpack.config'); 8 | 9 | const bundler = webpack(webpackConfig); 10 | 11 | // =========================================================================== 12 | // CONFIG 13 | // =========================================================================== 14 | const PATHS = webpackConfig.data.PATHS; 15 | 16 | // =========================================================================== 17 | // RUN 18 | // =========================================================================== 19 | (async() => { 20 | try { 21 | await clean(); 22 | await copyAssets(); 23 | await build(); 24 | console.log('Done.'); 25 | } 26 | catch (err) { 27 | console.error(err.toString()); 28 | } 29 | })(); 30 | 31 | // =========================================================================== 32 | // TASKS 33 | // =========================================================================== 34 | /** 35 | * Empty DIST directory 36 | */ 37 | function clean() { 38 | console.log('Cleaning DIST directory.'); 39 | return fs.emptyDirSync(PATHS.build()); 40 | } 41 | 42 | 43 | /** 44 | * Async, copy all non-(js|css) assets to DIST 45 | */ 46 | function copyAssets() { 47 | console.log('Copying assets.'); 48 | 49 | /** 50 | * Copy in parallel, resolve when all are complete. 51 | */ 52 | return new Promise((resolve, reject) => { 53 | const completedStack = []; // track completed copies 54 | 55 | // get files 56 | glob('src/**/*.!(scss|js)', (err, files) => { 57 | if (err) {return reject(err);} 58 | 59 | // for each file 60 | for (const file of files) { 61 | // copy to DIST, update completed stack 62 | const fileDest = getDest(file); 63 | fs.copy(file, fileDest, setComplete(files, fileDest)); 64 | } 65 | }); 66 | 67 | /** 68 | * Resolve promise when all files are copied 69 | */ 70 | function setComplete(files, fileDest) { 71 | return (err, file) => { 72 | if (err) {return reject(err);} 73 | 74 | // add current file to completed stack 75 | completedStack.push([ file, fileDest ]); 76 | 77 | // when completed stack matches initial list 78 | if (completedStack.length === files.length) { 79 | // resolve promise 80 | return resolve(completedStack); 81 | } 82 | }; 83 | } 84 | }); 85 | } 86 | 87 | /** 88 | * Async, run webpack 89 | */ 90 | function build() { 91 | console.log('Running webpack build.'); 92 | return new Promise((resolve, reject) => { 93 | bundler.run((err, stats) => (err ? reject(err) : resolve(stats))); 94 | }); 95 | } 96 | 97 | // =========================================================================== 98 | // UTILS 99 | // =========================================================================== 100 | 101 | /** 102 | * Map file locations from source to dist. 103 | */ 104 | function getDest(file) { 105 | return PATHS.build(file.replace(`sass${path.sep}`, '').replace(`src${path.sep}`, '')); 106 | } 107 | -------------------------------------------------------------------------------- /_start.js: -------------------------------------------------------------------------------- 1 | /* eslint default-case:0 */ 2 | global.watch = true; 3 | 4 | const path = require('path'); 5 | const fs = require('fs-extra'); 6 | const browserSync = require('browser-sync').create(); 7 | const webpack = require('webpack'); 8 | const webpackDevMiddleware = require('webpack-dev-middleware'); 9 | const webpackHotMiddleware = require('webpack-hot-middleware'); 10 | const htmlInjector = require('bs-html-injector'); 11 | const webpackConfig = require('./webpack.config'); 12 | 13 | const bundler = webpack(webpackConfig); 14 | 15 | // =========================================================================== 16 | // CONFIG 17 | // =========================================================================== 18 | const PATHS = webpackConfig.data.PATHS; 19 | const PROXY_TARGET = webpackConfig.data.PROXY_TARGET; 20 | const bsOptions = { 21 | files: [ 22 | { 23 | // scss|js managed by webpack 24 | match: [ 'src/**/*.!(scss|js)' ], 25 | // manually sync everything else 26 | fn: synchronize, 27 | }, 28 | ], 29 | 30 | proxy: { 31 | // proxy local WP install 32 | target: PROXY_TARGET, 33 | 34 | middleware: [ 35 | // converts browsersync into a webpack-dev-server 36 | webpackDevMiddleware(bundler, { 37 | publicPath: webpackConfig.output.publicPath, 38 | noInfo: true, 39 | }), 40 | 41 | // hot update js && css 42 | webpackHotMiddleware(bundler), 43 | ], 44 | }, 45 | 46 | //this gets annoying 47 | open: false, 48 | }; 49 | 50 | // setup html injector, only compare differences within outer most div (#page) 51 | // otherwise, it would replace the webpack HMR scripts 52 | browserSync.use(htmlInjector, { restrictions: [ '#page' ] }); 53 | 54 | // =========================================================================== 55 | // RUN 56 | // =========================================================================== 57 | // clean -> ensure 'style.css' -> run browsersync 58 | fs.emptyDir(PATHS.build(), () => ( 59 | fs.ensureFile(PATHS.build('style.css'), () => ( 60 | browserSync.init(bsOptions) 61 | )) 62 | ) 63 | ); 64 | 65 | // =========================================================================== 66 | // UTILS 67 | // =========================================================================== 68 | /** 69 | * Handle file events, sync updates. 70 | */ 71 | function synchronize(event, file) { 72 | // copy/remove file 73 | switch (event) { 74 | case 'add': 75 | fs.copy(file, getDest(file)); 76 | break; 77 | case 'change': 78 | fs.copy(file, getDest(file)); 79 | break; 80 | case 'unlink': 81 | fs.remove(getDest(file)); 82 | break; 83 | case 'unlinkDir': 84 | fs.remove(getDest(file)); 85 | break; 86 | } 87 | 88 | // activate html injector 89 | if (file.endsWith('php')) {htmlInjector();} 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress_theme", 3 | "version": "0.0.1", 4 | "description": "Simple example of webpack + hmr + browsersync + php (wordpress) ", 5 | "main": "_start.js", 6 | "scripts": { 7 | "start": "node _start.js", 8 | "build": "babel-node _build.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/bionikspoon/webpack-hmr-wordpress.git" 13 | }, 14 | "author": "Manu Phatak ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/bionikspoon/webpack-hmr-wordpress/issues" 18 | }, 19 | "homepage": "https://github.com/bionikspoon/webpack-hmr-wordpress#readme", 20 | "dependencies": { 21 | "malarkey": "1.3.3" 22 | }, 23 | "devDependencies": { 24 | "babel-core": "6.7.6", 25 | "babel-eslint": "6.0.2", 26 | "babel-loader": "6.2.4", 27 | "babel-plugin-transform-async-to-generator": "6.7.4", 28 | "babel-polyfill": "6.7.4", 29 | "babel-preset-es2015": "6.6.0", 30 | "browser-sync": "2.11.2", 31 | "bs-html-injector": "3.0.1", 32 | "css-loader": "0.23.1", 33 | "eslint": "2.7.0", 34 | "eslint-config-airbnb": "6.2.0", 35 | "eslint-plugin-react": "4.3.0", 36 | "extract-text-webpack-plugin": "1.0.1", 37 | "fs-extra": "0.26.7", 38 | "fs-promise": "0.5.0", 39 | "glob": "7.0.3", 40 | "node-sass": "3.4.2", 41 | "npm-install-webpack-plugin": "3.0.0", 42 | "sass-loader": "3.2.0", 43 | "style-loader": "0.13.1", 44 | "webpack": "1.12.14", 45 | "webpack-dev-middleware": "1.6.1", 46 | "webpack-hot-middleware": "2.10.0", 47 | "write-file-webpack-plugin": "3.1.8" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 2 | > 3 | 4 | 5 | 6 | 7 | > 8 |
9 |

Hello World. 

10 |

I like turtles!!!

11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | const activateMalarkey = require('./module/title'); 2 | document.addEventListener('DOMContentLoaded', loadingComplete); 3 | 4 | 5 | function loadingComplete() { 6 | // eslint-disable-next-line no-console 7 | console.log('Page Loaded! Activating malarkey.'); 8 | activateMalarkey(); 9 | } 10 | 11 | 12 | if (module.hot) { 13 | module.hot.accept('./module/title', () => { 14 | location.reload(); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/js/module/title.js: -------------------------------------------------------------------------------- 1 | const malarkey = require('malarkey'); 2 | 3 | module.exports = function activate() { 4 | const elem = document.querySelector('.title'); 5 | const opts = { loop: true }; 6 | 7 | activate.handle = malarkey(elem, opts); 8 | 9 | activate.handle 10 | .delete() 11 | .type('It Works!!!').pause().delete() 12 | .type('JS + SASS HMR in WordPress.').pause().delete() 13 | .type('Hello World').pause(); 14 | }; 15 | -------------------------------------------------------------------------------- /src/sass/_elements.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: monospace; 3 | } 4 | -------------------------------------------------------------------------------- /src/sass/style.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | Theme Name: theme_name 3 | Theme URI: https://github.com/bionikspoon/_msp-theme 4 | Author: Manu Phatak 5 | Author URI: https://manuphatak.com 6 | Description: Theme for manuphatak.com. 7 | Version: 0.0.1 8 | License: MIT 9 | License URI: https://opensource.org/licenses/MIT 10 | */ 11 | 12 | body { 13 | background-color: lightyellow; 14 | } 15 | 16 | @import "elements"; 17 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint default-case:0 */ 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const NPMInstallPlugin = require('npm-install-webpack-plugin'); 5 | const WriteFilePlugin = require('write-file-webpack-plugin'); 6 | const path = require('path'); 7 | 8 | // =========================================================================== 9 | // CONSTANTS 10 | // =========================================================================== 11 | const THEME_NAME = 'theme_name'; 12 | const PROXY_TARGET = 'local.wordpress.dev'; 13 | const HOST = 'localhost'; 14 | const PORT = 3000; 15 | const PATHS = { 16 | src: unipath('src'), 17 | build: unipath(`/home/manu/Code/wordpress-themes/${THEME_NAME}`), 18 | modules: unipath('node_modules'), 19 | base: unipath('.'), 20 | }; 21 | 22 | const LOADER_INCLUDES = [ PATHS.src() ]; 23 | 24 | const DEVELOPMENT = 'development'; 25 | const PRODUCTION = 'production'; 26 | 27 | // =========================================================================== 28 | // SETUP ENV 29 | // =========================================================================== 30 | const TARGET = process.env.npm_lifecycle_event; 31 | const ENV = getEnv(TARGET); 32 | const WATCH = global.watch || false; 33 | 34 | // =========================================================================== 35 | // CONFIG EXPORT 36 | // =========================================================================== 37 | module.exports = { 38 | entry: getEntry(ENV), 39 | 40 | output: { 41 | path: PATHS.build(), 42 | publicPath: ENV === PRODUCTION ? '/' : `//${HOST}:${PORT}/wp-content/themes/${THEME_NAME}/`, 43 | filename: 'js/[name].js', 44 | sourceMapFilename: '[file].map', 45 | }, 46 | 47 | module: { 48 | loaders: getLoaders(ENV), 49 | }, 50 | 51 | devtool: ENV === PRODUCTION ? 'source-map' : 'inline-source-map', 52 | 53 | plugins: getPlugins(ENV), 54 | 55 | target: 'web', 56 | 57 | watch: WATCH, 58 | data: { 59 | THEME_NAME, 60 | PROXY_TARGET, 61 | PATHS, 62 | }, 63 | }; 64 | 65 | 66 | // =========================================================================== 67 | // CONFIG ENV DEFINITIONS 68 | // =========================================================================== 69 | function getEntry(env) { 70 | const entry = {}; 71 | entry.main = [ PATHS.src('js', 'main.js') ]; 72 | entry.style = []; 73 | entry.vendor = Object.keys(require('./package.json').dependencies); 74 | 75 | switch (env) { 76 | case DEVELOPMENT: 77 | entry.main.unshift('webpack/hot/only-dev-server'); 78 | entry.main.unshift(`webpack-hot-middleware/client?http://${HOST}:${PORT}`); 79 | entry.main.push(PATHS.src('sass', 'style.scss')); 80 | break; 81 | 82 | case PRODUCTION: 83 | entry.style.push(PATHS.src('sass', 'style.scss')); 84 | break; 85 | } 86 | return entry; 87 | } 88 | 89 | function getLoaders(env) { 90 | const JS_LOADER = { 91 | test: /\.js$/, 92 | include: LOADER_INCLUDES, 93 | loader: 'babel', 94 | query: { 95 | cacheDirectory: true, 96 | }, 97 | }; 98 | 99 | const loaders = [ 100 | JS_LOADER, 101 | ]; 102 | 103 | switch (env) { 104 | case PRODUCTION: 105 | loaders.push({ 106 | test: /\.s?css$/, 107 | include: LOADER_INCLUDES, 108 | loader: ExtractTextPlugin.extract(( 109 | '' 110 | + 'css' 111 | + '?sourceMap' 112 | + '!' 113 | + 'sass' 114 | + '?sourceMap' 115 | )), 116 | }); 117 | break; 118 | 119 | case DEVELOPMENT: 120 | loaders.push({ 121 | test: /\.s?css$/, 122 | includes: LOADER_INCLUDES, 123 | loaders: [ 124 | 'style' 125 | + '?sourceMap', 126 | 'css' 127 | + '?sourceMap', 128 | 'sass' 129 | + '?sourceMap', 130 | ], 131 | }); 132 | break; 133 | } 134 | 135 | return loaders; 136 | } 137 | 138 | function getPlugins(env) { 139 | const plugins = [ 140 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(env) }), 141 | ]; 142 | 143 | switch (env) { 144 | 145 | case PRODUCTION: 146 | plugins.push(new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })); 147 | break; 148 | 149 | case DEVELOPMENT: 150 | plugins.push(new NPMInstallPlugin({ save: true })); 151 | plugins.push(new webpack.HotModuleReplacementPlugin()); 152 | plugins.push(new webpack.NoErrorsPlugin()); 153 | plugins.push(new WriteFilePlugin()); 154 | break; 155 | } 156 | 157 | plugins.push(new ExtractTextPlugin('[name].css')); 158 | 159 | return plugins; 160 | } 161 | 162 | // =========================================================================== 163 | // UTILS 164 | // =========================================================================== 165 | function getEnv(target) { 166 | switch (target) { 167 | case 'start': 168 | return DEVELOPMENT; 169 | 170 | case 'build': 171 | return PRODUCTION; 172 | 173 | case 'stats': 174 | return PRODUCTION; 175 | 176 | default: 177 | return DEVELOPMENT; 178 | } 179 | } 180 | 181 | function unipath(base) { 182 | return function join(/* ...paths */) { 183 | // eslint-disable-next-line prefer-rest-params 184 | const _paths = [ base ].concat(Array.from(arguments)); 185 | return path.resolve(path.join.apply(null, _paths)); 186 | }; 187 | } 188 | --------------------------------------------------------------------------------