├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── README.md ├── bin └── hjs-dev-server.js ├── examples ├── assets-and-index-html │ ├── .babelrc │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Hello.js │ │ ├── andyet.png │ │ ├── andyet.svg │ │ ├── app.js │ │ └── styles.styl │ └── webpack.config.js ├── just-assets-no-html │ ├── .babelrc │ ├── README.md │ ├── bring-your-own.html │ ├── package.json │ ├── src │ │ ├── Hello.js │ │ ├── app.js │ │ └── styles.styl │ └── webpack.config.js └── prerendered-html-files │ ├── .babelrc │ ├── README.md │ ├── package.json │ ├── src │ ├── app.js │ ├── home-page.js │ ├── other-page.js │ └── styles.styl │ └── webpack.config.js ├── index.js ├── lib ├── base-config.js ├── get-package.js ├── html-plugin.js ├── installed-hot-loaders.js ├── installed-style-loaders.js └── is-installed.js ├── package.json └── resources ├── hjs-webpack-localhost.crt └── hjs-webpack-localhost.key /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = spaces 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | examples/**/public/* 3 | npm-debug.log 4 | package-lock.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### 9.2.0 4 | - Update dependencies to their latest versions, including webpack to version 3 ([#349](https://github.com/HenrikJoreteg/hjs-webpack/pull/349)) 5 | - Use UglifyJS directly to make use of a version that supports ES2015 code ([#349](https://github.com/HenrikJoreteg/hjs-webpack/pull/349)) 6 | 7 | ### 9.1.0 8 | - Update examples to confirm with new linting rules ([#333](https://github.com/HenrikJoreteg/hjs-webpack/pull/333)) 9 | - Add support for using different NODE_ENV values than `production` in production build 10 | ([#332](https://github.com/HenrikJoreteg/hjs-webpack/pull/332)) 11 | - Add CORS headers to response on static files served by dev server 12 | ([#242](https://github.com/HenrikJoreteg/hjs-webpack/pull/242)) 13 | - Add support for using `postcss.config.js` to configure PostCSS 14 | ([#327](https://github.com/HenrikJoreteg/hjs-webpack/pull/327)) 15 | 16 | ### 9.0.0 17 | - Add support for webpack 2. Although there are no differences in the API, this version requires you 18 | to migrate to webpack@^2.2.1. You will also have to update your loader dependencies to their newest majors 19 | as well, as many of them have come out with webpack 2 support. 20 | If you have extended the configuration created by `hjs-webpack`, you will have to migrate those changes as 21 | well. Please have a look at the [official migration guide](https://webpack.js.org/guides/migrating/) for 22 | advice on how to do this. 23 | 24 | ### 8.4.3 25 | - Do not replace all environment variables in production [#296](https://github.com/HenrikJoreteg/hjs-webpack/issues/296) 26 | 27 | ### 8.4.2 28 | - Change how default name and version are specified for [#255](https://github.com/HenrikJoreteg/hjs-webpack/issues/255) 29 | 30 | ### 8.4.1 31 | - Add `compression` to dependencies [#291](https://github.com/HenrikJoreteg/hjs-webpack/issues/291) 32 | 33 | ### 8.4.0 34 | - Add `compression` option to dev server [#240](https://github.com/HenrikJoreteg/hjs-webpack/issues/240) 35 | - Add `pug-loader` to work with `.pug` files (and be backward compatible with `.jade` files) [#271](https://github.com/HenrikJoreteg/hjs-webpack/issues/271) 36 | - Add option to set `devtool` when `isDev` is true [#276](https://github.com/HenrikJoreteg/hjs-webpack/issues/276) 37 | - Use a default `package.name` and `package.version` so it doesn't error without them [#255](https://github.com/HenrikJoreteg/hjs-webpack/issues/255) 38 | 39 | ### 8.3.0 40 | - Add support for `webpack-visualizer-plugin` [#190](https://github.com/HenrikJoreteg/hjs-webpack/issues/190) 41 | - Throw error if trying to clear a directory containing the current working directory [#186](https://github.com/HenrikJoreteg/hjs-webpack/issues/186) 42 | - Allow setting uglifyjs options from config [#81](https://github.com/HenrikJoreteg/hjs-webpack/issues/81) 43 | 44 | ### 8.2.0 45 | - Add file extension `.tsx` to typescript loader [#223](https://github.com/HenrikJoreteg/hjs-webpack/pull/223) 46 | - Update `http-proxy-middleware` [#216](https://github.com/HenrikJoreteg/hjs-webpack/pull/216) 47 | 48 | ### 8.1.0 49 | - Add support for dev server proxies with `http-proxy-middleware` [#198](https://github.com/HenrikJoreteg/hjs-webpack/pull/198) 50 | 51 | ### 8.0.0 52 | Since hot module reloading can be done in many different forms (see [this blog post](https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf#.r3jfruhdm) and [this PR](https://github.com/reactjs/redux/pull/1455/files) for more info), `hjs-webpack` no longer looks at which loaders/plugins you have installed to determine if HMR should be turned on or off, and instead only looks at the config option `devServer.hot` to determine if the HMR entry path and plugins should be added to the config. 53 | 54 | By default, HMR has always been set to `true`, but previously if you didn't have a specific set of plugins installed `hjs-webpack` would set it to `false`. This is no longer the case, and is the reason why this is a major update. **If you are not using HMR, then you now need to set `devServer.hot = false` in your config to turn it off.** 55 | 56 | ### 7.3.2 57 | - Update `webpack-hot-middleware` to `^2.8.1` 58 | 59 | ### 7.3.1 60 | - Update dependencies, and pin `webpack-hot-middleware` to `2.7.1` for now to avoid [this issue](https://github.com/glenjamin/webpack-hot-middleware/issues/80) 61 | 62 | ### 7.3.0 63 | - Add option for enabling zoom on mobile devices [#176](https://github.com/HenrikJoreteg/hjs-webpack/issues/176) 64 | - Move ` 334 | ``` 335 | 336 | Note the `` tag and mobile viewport settings are there by default. 337 | 338 | The `` followed by the main script tag is also intentional. The ordering ensures we don't have to wait for `DOMReady` in our clientside code, you can safely assume that *both* `document.body` and `document.head` will be available when your script executes. 339 | 340 | If you just want to do JS and CSS and handle all the html yourself, simply add `html: false` to your config (see examples directory for example). 341 | 342 | **using an `html` function to generate specific files** 343 | 344 | This is where it gets interesting. Imagine pre-rendering all known structural content for a Native Web App to static files. Users get pixels on the screen immediately, your JS takes over when downloaded. If you're using React, this "taking over" can be completely seamless and invisible to the user. It's also possible with this approach to write an app that works entirely without JS. See the [prerendered-html-files example](https://github.com/HenrikJoreteg/hjs-webpack/tree/master/examples/prerendered-html-files). 345 | 346 | Your function should produce an object. 347 | 348 | Each key in the object is a filename, and its value is a string to be written to disc. 349 | 350 | If you simply specify `html: true` it will do the following by default: 351 | 352 | ```js 353 | html: function (context) { 354 | return { 355 | 'index.html': context.defaultTemplate() 356 | } 357 | } 358 | ``` 359 | 360 | So if you want to produce other files, you can do so by adding them to the returned object: 361 | 362 | ```js 363 | html: function (context) { 364 | return { 365 | 'index.html': context.defaultTemplate(), 366 | 367 | // if you build it entirely yourself it should be a complete HTML document 368 | // using whatever templating system you want 369 | 'other.html': '

Hello World

' 370 | } 371 | } 372 | ``` 373 | 374 | **async version** 375 | 376 | ```js 377 | html: function (context, callback) { 378 | // do whatever async stuff generate result object 379 | // and pass it to the callback instead 380 | db.fetchData(function (err, data) { 381 | callback(null, { 382 | 'index.html': buildHTML(data), 383 | 'other.html': doSomethingElse(data) 384 | }) 385 | }) 386 | } 387 | ``` 388 | 389 | **The context argument** 390 | 391 | Your `html` function will be called with a context object that contains the following: 392 | 393 | 1. `context.main`: the name of the generated JS file 394 | 2. `context.css`: the name of the generated CSS file. This only exists if `isDev` is `false`, since in development mode the css bundle is inserted dynamically into the document by the [`style-loader`](https://github.com/webpack/style-loader). 395 | 3. `context.defaultTemplate()` a convenience method you can call to generate the basic HTML shown above. This takes a few options too if you just want to make minor tweaks. If you want to do more, just don't use the default template, generate your own instead. The options are: 396 | - `{html: '
Some custom markup
'}` This markup will be added inside the `` tag. By default it adds a `
` as a mount target for React apps. 397 | - `{charset: 'utf-8'}` what charset to set 398 | - `{title: 'your app'}` sets `` 399 | - `{head: 'any string'}` anything else you want to put in the `head`, other meta tags, or whatnot. 400 | - `{metaViewport: boolean|object}` set to false if you don't want the default viewport tag. Set to an object with `userScalable` true if you don't want to block user-zoom on mobile 401 | - `{publicPath: 'http://mycdn.com/'}` (default `/`) pass in path that will prefix the generated css/js files in the template. Note, there is `output.publicPath` provided by webpack, but doesn't easily allow for switching based on envirnoment. In this method we've got access to `context.isDev` and can easily switch based on that. 402 | - `{metaTags: {}}` lets you easily add `<meta>` tags to the document head. Takes an object where the key is the `name` and the value is the `content`. 403 | - `{lang: 'en-US'}` sets the `lang` attribute on the `<html>` tag. 404 | 4. `context.isDev`: boolean specifying whether or not we're in dev mode. 405 | 5. `context.package`: the parsed `package.json` file as an object. 406 | 6. `context.stats`: the stats object returned by webpack. Of likely interest is `context.stats.hash` (a hash of current build). `context.stats.assets` is an array of all the assets that will be generated. This can be useful for generating cache manifests, etc. Overall, this is a big object that lists all the modules in your whole app. You likely won't need most of it, but it's all there in case you do. ([A sample can be found here](https://raw.githubusercontent.com/webpack/analyse/master/app/pages/upload/example.json)). 407 | 408 | 409 | ### `serveCustomHtmlInDev` (optional, boolean, default is `true`) 410 | 411 | By default, if you supply an `html` function it will always be used, whether you're in development mode or not. 412 | 413 | Set this option to `false` to only use your `html` function when building for production. Note, that `.isDev` is attached to the context object passed to the `html` function as described above, so alternately you could just use that value to branch your logic within that function. Using this option circumvents the custom `html` function entirely during development. 414 | 415 | ## Proxy 416 | 417 | The dev server uses [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware) to optionally proxy requests to a separate, possibly external, backend server. Proxies can be specified with `devServer.proxy`. This can be a single proxy, or an array of proxies. The proxy context and options are passed directly to `http-proxy-middleware`. 418 | 419 | ```js 420 | getConfig({ 421 | in: 'src/app.js', 422 | out: 'public', 423 | clearBeforeBuild: true, 424 | 425 | // Use devServer.proxy to specify proxies 426 | devServer: { 427 | proxy: { 428 | context: "/api", 429 | options: { 430 | target: "http://localhost:3001", 431 | pathRewrite: { 432 | "^/api": "" 433 | } 434 | } 435 | } 436 | } 437 | }) 438 | ``` 439 | 440 | ## Developing on multiple devices at once 441 | 442 | If you're building an app that you want to look good on all devices it's nice to be able to run them all at once. 443 | 444 | Hotloading makes this extremely nice and convenient. 445 | 446 | If you're on a Mac, this is fairly simple. Just add a `hostname` option to your config like so: 447 | 448 | ```js 449 | module.exports = getConfig({ 450 | in: 'src/app.js', 451 | out: 'public', 452 | 453 | // set this to whatever your machine name is 454 | // plus `.local` 455 | // my machine is `loki` so I do: 456 | hostname: 'loki.local' 457 | }) 458 | ``` 459 | 460 | Now when you run the development instead of going to localhost open: `http://{{yourmachine}}.local:3000` on any device that's on your local network, they should all connect and all hotload your style and JS changes. 461 | 462 | ## Extending `hjs-webpack` 463 | 464 | `hjs-webpack` is not designed to take all the same options as `webpack`. Instead it is designed to take the [config options](#config-options) listed above and return an object that is then consumed by `webpack`. That means that if you want to change/add/remove anything in the config, it is the same as manipulating any JavaScript object. 465 | 466 | Here's an example where `hjs-webpack` is used to create the base `webpack` config, and then it is manipulated to add a new loader, plugin, and option. 467 | 468 | ```js 469 | var webpack = require('webpack') 470 | var getConfig = require('hjs-webpack') 471 | var config = getConfig(myHjsWebpackOptions) 472 | 473 | // Add xml-loader 474 | config.module.rules.push({ test: /\.xml$/, use: ['xml-loader'] }) 475 | 476 | // Add webpack PrefetchPlugin 477 | config.plugins.push(new webpack.PrefetchPlugin([context], request)) 478 | 479 | // Add a separate entry point for jQuery 480 | config.resolve.alias = { jquery:'jquery/src/jquery.js' } 481 | config.plugins.push( 482 | new webpack.ProvidePlugin({ 483 | jQuery: 'jquery', 484 | $: 'jquery', 485 | 'window.jQuery':'jquery' 486 | }), 487 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js') 488 | ); 489 | config.entry = { 490 | // Add entries for vendors 491 | vendors: ['jquery'], 492 | // Reassign previous single entry to main entry 493 | main: config.entry 494 | }; 495 | 496 | // Export the newly manipulated config 497 | module.exports = config 498 | ``` 499 | 500 | 501 | 502 | ### Changing Babel config 503 | 504 | Since `hjs-webpack` already has a babel loader, the easiest way to tweak Babel settings is to create a file at the root of your project called `.babelrc` that contains config settings. See [babelrc docs](https://babeljs.io/docs/usage/babelrc/) for more options. 505 | 506 | There are some babel presets that work well with `hjs-webpack`. You can check out an example of using presets in the [examples directory](./examples). There's one with [hot reloading](./examples/assets-and-index-html/.babelrc) and one [without](/examples/just-assets-no-html/.babelrc). You'll need to install these presets just like any other dev dependencies. 507 | 508 | Here's a quick example if you like copy/pasting: 509 | 510 | ```sh 511 | npm install babel-preset-es2015 babel-preset-react babel-preset-react-hmre --save-dev 512 | ``` 513 | 514 | and then your `.babelrc` 515 | 516 | ```json 517 | { 518 | "presets": ["es2015", "react"], 519 | "env": { 520 | "development": { 521 | "presets": ["react-hmre"] 522 | } 523 | } 524 | } 525 | ``` 526 | 527 | 528 | 529 | ## Credits 530 | 531 | This is mostly just some add-ons to [webpack](http://webpack.github.io/) so most of the credit goes there. 532 | 533 | If you're interested in building apps this way, watch the free section of the tutorials at http://learn.humanjavascript.com. It shows basic usage of this module. Also, you can follow me on twitter [@HenrikJoreteg](http://twitter.com/henrikjoreteg). 534 | 535 | Big thanks to co-maintainer [@LukeKarrys](http://twitter.com/lukekarrys) for helping find/fix some really annoying bugs. 536 | 537 | ## Contributing/Forking 538 | 539 | Beware that this is all highly opinionated and contains a lot of personal preferences. If you want to add or remove major things, feel free to open issues or send PRs, but you may just want to fork it. 540 | 541 | ## Changelog 542 | 543 | See the [`CHANGELOG.md`](CHANGELOG.md) 544 | 545 | ## license 546 | 547 | MIT 548 | -------------------------------------------------------------------------------- /bin/hjs-dev-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Based on 4 | // https://github.com/gaearon/react-transform-boilerplate/blob/master/devServer.js 5 | 6 | var fs = require('fs') 7 | var path = require('path') 8 | var express = require('express') 9 | var webpack = require('webpack') 10 | var assign = require('lodash.assign') 11 | var compress = require('compression') 12 | var httpProxyMiddleware = require('http-proxy-middleware') 13 | 14 | var configFile = process.argv[2] || 'webpack.config.js' 15 | var config 16 | try { 17 | config = require(path.join(process.cwd(), configFile)) 18 | } catch (e) { 19 | console.error(e.stack) 20 | console.error( 21 | 'Failed to load webpack config, please use like this\n' + 22 | 'hjs-dev-server.js webpack.config.js\n' 23 | ) 24 | process.exit(1) 25 | } 26 | 27 | var serverConfig = config.devServer 28 | var https = serverConfig.https 29 | var app = express() 30 | 31 | var createServer = require(https ? 'https' : 'http').createServer 32 | var server 33 | 34 | if (serverConfig.compress) { 35 | app.use(compress()) 36 | } 37 | 38 | if (https) { 39 | var httpsConfig = { 40 | key: fs.readFileSync(path.resolve(__dirname, '../resources/hjs-webpack-localhost.key')), 41 | cert: fs.readFileSync(path.resolve(__dirname, '../resources/hjs-webpack-localhost.crt')) 42 | } 43 | 44 | if (typeof https === 'object') { 45 | assign(httpsConfig, https) 46 | } 47 | 48 | server = createServer(httpsConfig, app) 49 | } else { 50 | server = createServer(app) 51 | } 52 | 53 | var compiler = webpack(config) 54 | 55 | if (serverConfig.proxy) { 56 | if (!Array.isArray(serverConfig.proxy)) { 57 | serverConfig.proxy = [serverConfig.proxy] 58 | } 59 | serverConfig.proxy.forEach(function (proxyConfig) { 60 | var proxy = httpProxyMiddleware(proxyConfig.context, proxyConfig.options) 61 | app.use(function (req, res, next) { 62 | next() 63 | }, proxy) 64 | }) 65 | } 66 | 67 | if (serverConfig.historyApiFallback) { 68 | app.use(require('connect-history-api-fallback')({ 69 | verbose: false 70 | })) 71 | } 72 | 73 | app.use(require('webpack-dev-middleware')(compiler, serverConfig)) 74 | 75 | if (serverConfig.hot) { 76 | app.use(require('webpack-hot-middleware')(compiler)) 77 | } 78 | 79 | if (serverConfig.contentBase) { 80 | var allowCrossDomain = function (req, res, next) { 81 | res.header('Access-Control-Allow-Origin', '*') 82 | res.header('Access-Control-Allow-Methods', 'GET,OPTIONS') 83 | res.header('Access-Control-Allow-Headers', 'Content-Type') 84 | 85 | next() 86 | } 87 | app.use(allowCrossDomain) 88 | app.use(express.static(serverConfig.contentBase)) 89 | } 90 | 91 | server.listen(serverConfig.port, serverConfig.hostname, function (err) { 92 | if (err) { 93 | console.error(err) 94 | return 95 | } 96 | 97 | var protocol = https ? 'https' : 'http' 98 | console.log('Listening at ' + protocol + '://' + serverConfig.hostname + ':' + serverConfig.port) 99 | }) 100 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/README.md: -------------------------------------------------------------------------------- 1 | Simple, catch-all HTML file configuration. 2 | 3 | By default, hjs-webpack will create and serve a catchall HTML file that references your assets. 4 | 5 | ``` 6 | var getConfig = require('hjs-webpack') 7 | 8 | module.exports = getConfig({ 9 | in: './app.js', 10 | out: 'public', 11 | clearBeforeBuild: true 12 | }) 13 | ``` 14 | 15 | Try running `npm start` in this folder then opening localhost:3000/something and you'll still see the same HTML response. 16 | 17 | Running `npm run build` will produce a `public` directory with just minified JS, CSS, and a basic `index.html` file that references the built assets. 18 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assets-and-index-html", 3 | "version": "1.0.0", 4 | "author": "Henrik Joreteg <henrik@andyet.net>", 5 | "dependencies": { 6 | "react": "^15.0.1" 7 | }, 8 | "devDependencies": { 9 | "autoprefixer": "^6.3.6", 10 | "babel": "^6.5.2", 11 | "babel-core": "^6.7.6", 12 | "babel-loader": "^6.2.4", 13 | "babel-preset-es2015": "^6.6.0", 14 | "babel-preset-react": "^6.5.0", 15 | "babel-preset-react-hmre": "^1.1.1", 16 | "css-loader": "^0.26.4", 17 | "file-loader": "^0.10.1", 18 | "hjs-webpack": "^9.0.0", 19 | "postcss-loader": "^1.3.3", 20 | "react-dom": "^15.0.1", 21 | "style-loader": "^0.13.1", 22 | "stylus": "^0.54.2", 23 | "stylus-loader": "^3.0.1", 24 | "url-loader": "^0.5.7", 25 | "webpack": "^2.2.1", 26 | "yeticss": "^7.3.0" 27 | }, 28 | "license": "MIT", 29 | "main": "app.js", 30 | "scripts": { 31 | "start": "hjs-dev-server", 32 | "build": "NODE_ENV=production webpack" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/src/Hello.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | const andyetImgSrc = require('./andyet.png') 3 | 4 | class Hello extends React.Component { 5 | render () { 6 | return ( 7 | <div className='container'> 8 | <header role='banner'> 9 | <h1>Assets + HTML Example</h1> 10 | </header> 11 | <p>Generates, base HTML, CSS, and JS on build</p> 12 | <img src={andyetImgSrc} style={{backgroundColor: '#e6eaed'}} /> 13 | </div> 14 | ) 15 | } 16 | } 17 | 18 | export default Hello 19 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/src/andyet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HenrikJoreteg/hjs-webpack/c8aa76632974819b4882ccceadb7b435725174ca/examples/assets-and-index-html/src/andyet.png -------------------------------------------------------------------------------- /examples/assets-and-index-html/src/andyet.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg width="184px" height="100px" viewBox="0 0 184 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> 3 | <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --> 4 | <title>Untitled 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import Hello from './Hello' 4 | 5 | import './styles.styl' 6 | 7 | render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/src/styles.styl: -------------------------------------------------------------------------------- 1 | @import 'yeticss' 2 | 3 | body 4 | border: 10px solid $gray-lighter 5 | 6 | header 7 | margin-top: 30px 8 | background-image: url('./andyet.svg') 9 | -------------------------------------------------------------------------------- /examples/assets-and-index-html/webpack.config.js: -------------------------------------------------------------------------------- 1 | var getConfig = require('hjs-webpack') 2 | 3 | module.exports = getConfig({ 4 | in: 'src/app.js', 5 | out: 'public', 6 | clearBeforeBuild: true 7 | }) 8 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/README.md: -------------------------------------------------------------------------------- 1 | Simple, no-html configuration 2 | 3 | All you have to do is serve HTML *somehow* that includes the following script tag. 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | As long as your `isDev` is true styles and JS should still be hotloaded if possible. Try running `npm start` in this folder then opening `http://localhost:3000/bring-your-own.html` it will serve the `bring-your-own.html` file since it's in the folder here, but running `npm run build` will produce a `public` directory with just minified JS and CSS assets. 10 | 11 | Note that this example has hot module replacement turned off. To turn it on, set `devServer.hot = true` in the webpack config, npm install `babel-preset-react-hmre`, and add this to the `.babelrc`. 12 | 13 | ``` 14 | "env": { 15 | "development": { 16 | "presets": ["react-hmre"] 17 | } 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/bring-your-own.html: -------------------------------------------------------------------------------- 1 | 2 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "just-assets-no-html", 3 | "version": "1.0.0", 4 | "author": "Henrik Joreteg ", 5 | "dependencies": { 6 | "react": "^15.0.1" 7 | }, 8 | "devDependencies": { 9 | "autoprefixer": "^6.3.6", 10 | "babel": "^6.5.2", 11 | "babel-core": "^6.7.6", 12 | "babel-loader": "^6.2.4", 13 | "babel-preset-es2015": "^6.6.0", 14 | "babel-preset-react": "^6.5.0", 15 | "css-loader": "^0.26.4", 16 | "hjs-webpack": "^9.0.0", 17 | "postcss-loader": "^1.3.3", 18 | "react-dom": "^15.0.1", 19 | "style-loader": "^0.13.1", 20 | "stylus": "^0.54.2", 21 | "stylus-loader": "^3.0.1", 22 | "webpack": "^2.2.1", 23 | "yeticss": "^7.3.0" 24 | }, 25 | "license": "MIT", 26 | "main": "app.js", 27 | "scripts": { 28 | "start": "hjs-dev-server", 29 | "build": "NODE_ENV=production webpack" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/src/Hello.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class Hello extends React.Component { 4 | render () { 5 | return ( 6 |
7 |
8 |

Assets Only Example

9 |
10 |

During dev, we just need something to include the script tag:

11 |

<script src="/app.js"></script>

12 |

Building only generates CSS and JS.

13 |
14 | ) 15 | } 16 | } 17 | 18 | export default Hello 19 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import Hello from './Hello' 4 | 5 | import './styles.styl' 6 | 7 | render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/src/styles.styl: -------------------------------------------------------------------------------- 1 | @import 'yeticss' 2 | 3 | body 4 | border: 10px solid $gray-lighter 5 | 6 | header 7 | margin-top: 30px 8 | -------------------------------------------------------------------------------- /examples/just-assets-no-html/webpack.config.js: -------------------------------------------------------------------------------- 1 | var getConfig = require('hjs-webpack') 2 | var isDev = process.env.NODE_ENV !== 'production' 3 | 4 | module.exports = getConfig({ 5 | in: 'src/app.js', 6 | out: 'public', 7 | isDev: isDev, 8 | html: false, 9 | clearBeforeBuild: true, 10 | devServer: { 11 | hot: false, 12 | contentBase: __dirname 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/README.md: -------------------------------------------------------------------------------- 1 | Here we generate multiple static HTML files. 2 | 3 | This can be especially interesting if used to pre-render known HTML with React. 4 | 5 | ```js 6 | var getConfig = require('hjs-webpack') 7 | 8 | module.exports = getConfig({ 9 | in: './app.js', 10 | out: 'public', 11 | clearBeforeBuild: true, 12 | html: function (data) { 13 | // here we return an object where each key is a file to be generated 14 | return { 15 | '200.html': data.defaultTemplate(), 16 | 'index.html': [ 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | '

Home Page

', 23 | '', 24 | '', 25 | '' 26 | ].join('') 27 | } 28 | } 29 | }) 30 | ``` 31 | 32 | **note** it can also be asynchronous and these can be anything (not just HTML strings) 33 | 34 | ```js 35 | html: function (data, cb) { 36 | cb(err, { 37 | 'index.html': '<...>', 38 | 'something.html': '<...>', 39 | 'cache.manifest': '...' 40 | }) 41 | } 42 | ``` 43 | 44 | ## Start in dev mode 45 | 46 | This just runs the `start` script in the `scripts` section of `package.json`. 47 | 48 | ``` 49 | npm start 50 | ``` 51 | 52 | In development, this doesn't get used. Instead during dev it just generates a simple default HTML page that includes the assets and is hotloaded, etc. 53 | 54 | ## Build to static 55 | 56 | This just runs the `build` script in the `scripts` section of `package.json`. 57 | 58 | ``` 59 | npm run build 60 | ``` 61 | 62 | Running `npm run build` will produce a `public` directory with just minified JS, CSS, and a each of the files specified by the HTML callback. 63 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pre-rendered-html", 3 | "version": "1.0.0", 4 | "author": "Henrik Joreteg ", 5 | "dependencies": { 6 | "ampersand-router": "^4.0.0", 7 | "react": "^15.0.1" 8 | }, 9 | "devDependencies": { 10 | "autoprefixer": "^6.3.6", 11 | "babel": "^6.5.2", 12 | "babel-core": "^6.7.6", 13 | "babel-loader": "^6.2.4", 14 | "babel-preset-es2015": "^6.6.0", 15 | "babel-preset-react": "^6.5.0", 16 | "babel-preset-react-hmre": "^1.1.1", 17 | "css-loader": "^0.26.4", 18 | "hjs-webpack": "^9.0.0", 19 | "postcss-loader": "^1.3.3", 20 | "react-dom": "^15.0.1", 21 | "style-loader": "^0.13.1", 22 | "stylus": "^0.54.2", 23 | "stylus-loader": "^3.0.1", 24 | "webpack": "^2.2.1", 25 | "yeticss": "^7.3.0" 26 | }, 27 | "license": "MIT", 28 | "main": "app.js", 29 | "scripts": { 30 | "start": "hjs-dev-server", 31 | "build": "NODE_ENV=production webpack" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import AmpRouter from 'ampersand-router' 4 | import HomePage from './home-page' 5 | import OtherPage from './other-page' 6 | 7 | import './styles.styl' 8 | 9 | const Router = AmpRouter.extend({ 10 | routes: { 11 | '': 'home', 12 | 'other': 'other' 13 | }, 14 | 15 | home: function () { 16 | render(, document.getElementById('root')) 17 | }, 18 | 19 | other: function () { 20 | render(, document.getElementById('root')) 21 | } 22 | }) 23 | 24 | let router = new Router() 25 | window.router = router 26 | router.history.start() 27 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/src/home-page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class HomePage extends React.Component { 4 | // quick and dirty internal nav handler 5 | onClick (e) { 6 | e.preventDefault() 7 | window.router.history.navigate('/other') 8 | } 9 | render () { 10 | return ( 11 |
12 |
13 |

Pre-rendering example

14 |
15 | go to /other 16 |

Note that navigating between these two pages also works with JS off

17 |

During dev things are handled by catchall/generated/live-reloaded dev-server

18 |

During build homepage and layout are pre-rendered.

19 |
20 | ) 21 | } 22 | } 23 | 24 | export default HomePage 25 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/src/other-page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class OtherPage extends React.Component { 4 | // quick and dirty internal nav handler 5 | onClick (e) { 6 | e.preventDefault() 7 | window.router.history.navigate('/') 8 | } 9 | render () { 10 | return ( 11 |
12 |
13 |

Other page

14 |
15 | go to / 16 |
17 | ) 18 | } 19 | } 20 | 21 | export default OtherPage 22 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/src/styles.styl: -------------------------------------------------------------------------------- 1 | @import 'yeticss' 2 | 3 | body 4 | border: 10px solid blue 5 | 6 | header 7 | margin-top: 30px 8 | transform: scale(0.5) 9 | -------------------------------------------------------------------------------- /examples/prerendered-html-files/webpack.config.js: -------------------------------------------------------------------------------- 1 | // this lets us require files with JSX/ES6 in them 2 | require('babel-core/register') 3 | 4 | // require React and our two React components 5 | var React = require('react') 6 | var ReactDOMServer = require('react-dom/server') 7 | var HomePage = require('./src/home-page').default 8 | var OtherPage = require('./src/other-page').default 9 | 10 | // our hjs-webpack, of course 11 | var getConfig = require('hjs-webpack') 12 | 13 | var config = getConfig({ 14 | in: 'src/app.js', 15 | out: 'public', 16 | clearBeforeBuild: true, 17 | html: function (data) { 18 | // use React's `renderToString` method to return an HTML string from our 19 | // components (dynamic values can be passed into `createElement` too) 20 | var homePageHtmlString = ReactDOMServer.renderToString(React.createElement(HomePage)) 21 | var otherPageHtmlString = ReactDOMServer.renderToString(React.createElement(OtherPage)) 22 | 23 | return { 24 | 'index.html': data.defaultTemplate({html: `
${homePageHtmlString}
`}), 25 | 'other/index.html': data.defaultTemplate({html: `
${otherPageHtmlString}
`}) 26 | 27 | // here we might also create an app manifest.json 28 | // app-manifest.json: JSON.stringify(something) 29 | 30 | // or perhaps an appcache manifest 31 | // cache.manifest: buildSomeString() 32 | } 33 | } 34 | }) 35 | 36 | config.module.rules[0].use = [{ 37 | loader: 'babel-loader', 38 | options: { 39 | env: { 40 | development: { presets: ['react-hmre'] } 41 | } 42 | } 43 | }] 44 | module.exports = config 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var rimraf = require('rimraf') 4 | var webpack = require('webpack') 5 | var defaults = require('lodash.defaults') 6 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 7 | var UglifyJsPlugin = require('uglifyjs-webpack-plugin') 8 | var containsPath = require('contains-path') 9 | var getBaseConfig = require('./lib/base-config') 10 | var getPackage = require('./lib/get-package') 11 | var installedStyleLoaders = require('./lib/installed-style-loaders') 12 | var installedHotLoaders = require('./lib/installed-hot-loaders') 13 | var isInstalled = require('./lib/is-installed') 14 | 15 | // default to `true` if running `hjs-dev-server` 16 | var isDev = (process.argv[1] || '').indexOf('hjs-dev-server') !== -1 17 | 18 | module.exports = function (opts) { 19 | checkRequired(opts) 20 | var outputFolder = path.resolve(opts.out) 21 | var cwd = process.cwd() 22 | 23 | // add in our defaults 24 | var spec = defaults(opts, { 25 | entry: path.resolve(opts.in), 26 | output: defaults(opts.output || {}, { 27 | path: outputFolder + '/', 28 | filename: null, 29 | cssFilename: null, 30 | hash: false, 31 | publicPath: '/' 32 | }), 33 | configFile: null, 34 | isDev: isDev, 35 | package: null, 36 | replace: null, 37 | https: false, 38 | port: 3000, 39 | hostname: 'localhost', 40 | html: true, 41 | urlLoaderLimit: 10000, 42 | clearBeforeBuild: false, 43 | serveCustomHtmlInDev: true, 44 | devServer: {}, 45 | uglify: opts.uglify 46 | }) 47 | 48 | spec.package = getPackage(spec.package) 49 | 50 | if (!spec.output.filename) { 51 | spec.output.filename = spec.isDev ? 'app.js' : buildFilename(spec.package, spec.output.hash, 'js') 52 | } 53 | 54 | if (!spec.output.cssFilename) { 55 | spec.output.cssFilename = spec.isDev ? 'app.css' : buildFilename(spec.package, spec.output.hash, 'css') 56 | } 57 | 58 | var config = getBaseConfig(spec) 59 | 60 | // check for any module replacements 61 | if (spec.replace) { 62 | for (var item in spec.replace) { 63 | // allow for simple strings 64 | if (typeof item === 'string') { 65 | var regex = new RegExp('^' + item + '$') 66 | } 67 | var newResource = spec.replace[item] 68 | if (typeof newResource === 'string') { 69 | newResource = path.resolve(newResource) 70 | } 71 | config.plugins.push(new webpack.NormalModuleReplacementPlugin(regex, newResource)) 72 | } 73 | } 74 | 75 | // check for any module definitions 76 | if (spec.define) { 77 | config.plugins.push(new webpack.DefinePlugin(spec.define)) 78 | } 79 | 80 | // dev specific stuff 81 | if (spec.isDev) { 82 | // debugging option 83 | // https://webpack.github.io/docs/configuration.html#devtool 84 | // https://github.com/HenrikJoreteg/hjs-webpack/issues/63 85 | // Supports original code (before transforms) with pretty good initial 86 | // build speed and good rebuild speed 87 | config.devtool = spec.devtool || 'cheap-module-eval-source-map' 88 | 89 | // Create our dev server config for use in bin/hjs-dev-server 90 | config.devServer = defaults(spec.devServer, { 91 | // For webpack-dev-middleware 92 | noInfo: true, 93 | quiet: false, 94 | lazy: false, 95 | publicPath: spec.output.publicPath, 96 | // Our own options for hjs-dev-server 97 | historyApiFallback: true, 98 | hot: true, 99 | contentBase: outputFolder, 100 | port: spec.port, 101 | https: spec.https, 102 | hostname: spec.hostname || spec.host 103 | }) 104 | 105 | // Enable Webpack HMR unless explictly disabled 106 | if (config.devServer.hot) { 107 | installedHotLoaders.load(config) 108 | } 109 | 110 | // Add optional loaders 111 | installedStyleLoaders.forEach(function (item) { 112 | config.module.rules.push(item.dev) 113 | }) 114 | 115 | // Add visualizer plugin 116 | if (isInstalled('webpack-visualizer-plugin')) { 117 | config.plugins.push( 118 | new (require('webpack-visualizer-plugin'))() 119 | ) 120 | } 121 | } else { 122 | // clear out output folder if so configured 123 | if (spec.clearBeforeBuild) { 124 | // Throw error if trying to clear output directory but it contains the cwd 125 | // See https://github.com/HenrikJoreteg/hjs-webpack/issues/186 126 | if (containsPath(cwd, outputFolder)) { 127 | throw new Error('Cannot clear out directory since it contains the current working directory.\nTried to clear ' + outputFolder + ' from ' + cwd) 128 | } 129 | 130 | // allow passing a glob (limit to within folder though) 131 | if (typeof spec.clearBeforeBuild === 'string') { 132 | // create the output folder if it doesn't exist 133 | // just for convenience 134 | if (!fs.existsSync(outputFolder)) { 135 | fs.mkdirSync(outputFolder) 136 | } 137 | rimraf.sync(outputFolder + '/' + spec.clearBeforeBuild) 138 | } else { 139 | rimraf.sync(outputFolder) 140 | fs.mkdirSync(outputFolder) 141 | } 142 | } 143 | 144 | // minify in production 145 | config.plugins.push( 146 | new UglifyJsPlugin(spec.uglify), 147 | new ExtractTextPlugin({ 148 | filename: spec.output.cssFilename, 149 | allChunks: true 150 | }), 151 | new webpack.DefinePlugin({ 152 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 153 | }) 154 | ) 155 | 156 | // Add optional loaders 157 | installedStyleLoaders.forEach(function (item) { 158 | config.module.rules.push(item.production) 159 | }) 160 | } 161 | 162 | return config 163 | } 164 | 165 | function buildFilename (pack, hash, ext) { 166 | return [ 167 | pack.name, 168 | // extract-text-plugin uses [contenthash] and webpack uses [hash] 169 | hash ? (ext === 'css' ? '[contenthash]' : '[hash]') : pack.version, 170 | ext || 'js' 171 | ].join('.') 172 | } 173 | 174 | function checkRequired (opts) { 175 | var props = ['out', 'in'] 176 | if (!opts || !props.every(function (prop) { return opts.hasOwnProperty(prop) })) { 177 | throw new Error('Must pass in options object with `in` and `out` properties') 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lib/base-config.js: -------------------------------------------------------------------------------- 1 | var HtmlPlugin = require('./html-plugin') 2 | var pick = require('lodash.pick') 3 | var isInstalled = require('./is-installed') 4 | 5 | module.exports = function getBaseConfig (spec) { 6 | var baseConfig = { 7 | entry: [ 8 | spec.entry 9 | ], 10 | output: { 11 | filename: spec.output.filename, 12 | path: spec.output.path, 13 | publicPath: spec.output.publicPath 14 | }, 15 | resolve: { 16 | extensions: [ 17 | '.js', 18 | '.jsx', 19 | '.json' 20 | ] 21 | }, 22 | plugins: [ 23 | new HtmlPlugin(pick(spec, [ 24 | 'html', 25 | 'isDev', 26 | 'serveCustomHtmlInDev', 27 | 'package' 28 | ])) 29 | ], 30 | module: { 31 | rules: [] 32 | } 33 | } 34 | 35 | var optionalBaseLoaders = [ 36 | { 37 | pkg: 'worker-loader', 38 | config: { 39 | test: /(^|\.)worker\.js$/, 40 | exclude: /node_modules/, 41 | use: ['worker-loader'] 42 | } 43 | }, 44 | { 45 | pkg: 'worker-loader', 46 | config: { 47 | test: /(^|\.)thread\.js$/, 48 | exclude: /node_modules/, 49 | use: [ 50 | { loader: 'worker-loader', options: { inline: true } } 51 | ] 52 | } 53 | }, 54 | { 55 | pkg: 'babel-loader', 56 | config: { 57 | test: /\.(js|jsx|babel)$/, 58 | exclude: /node_modules/, 59 | use: ['babel-loader'] 60 | } 61 | }, 62 | { 63 | pkg: 'coffee-loader', 64 | config: { 65 | test: /\.coffee$/, 66 | use: ['coffee-loader'] 67 | } 68 | }, 69 | { 70 | pkg: 'cjsx-loader', 71 | config: { 72 | test: /\.cjsx$/, 73 | use: ['coffee-loader', 'cjsx-loader'] 74 | } 75 | }, 76 | { 77 | pkg: 'awesome-typescript-loader', 78 | config: { 79 | test: /\.(ts|tsx)$/, 80 | use: ['awesome-typescript-loader'] 81 | } 82 | }, 83 | { 84 | pkg: 'livescript-loader', 85 | config: { 86 | test: /\.ls$/, 87 | use: ['livescript-loader'] 88 | } 89 | }, 90 | { 91 | pkg: 'url-loader', 92 | config: { 93 | test: /\.otf(\?\S*)?$/, 94 | use: [{ 95 | loader: 'url-loader', 96 | options: { limit: spec.urlLoaderLimit } 97 | }] 98 | } 99 | }, 100 | { 101 | pkg: 'url-loader', 102 | config: { 103 | test: /\.eot(\?\S*)?$/, 104 | use: [{ 105 | loader: 'url-loader', 106 | options: { limit: spec.urlLoaderLimit } 107 | }] 108 | } 109 | }, 110 | { 111 | pkg: 'url-loader', 112 | config: { 113 | test: /\.svg(\?\S*)?$/, 114 | use: [{ 115 | loader: 'url-loader', 116 | options: { mimetype: 'image/svg+xml', limit: spec.urlLoaderLimit } 117 | }] 118 | } 119 | }, 120 | { 121 | pkg: 'url-loader', 122 | config: { 123 | test: /\.ttf(\?\S*)?$/, 124 | use: [{ 125 | loader: 'url-loader', 126 | options: { mimetype: 'application/octet-stream', limit: spec.urlLoaderLimit } 127 | }] 128 | } 129 | }, 130 | { 131 | pkg: 'url-loader', 132 | config: { 133 | test: /\.woff2?(\?\S*)?$/, 134 | use: [{ 135 | loader: 'url-loader', 136 | options: { mimetype: 'application/font-woff', limit: spec.urlLoaderLimit } 137 | }] 138 | } 139 | }, 140 | { 141 | pkg: 'url-loader', 142 | config: { 143 | test: /\.(jpe?g|png|gif)$/, 144 | use: [{ 145 | loader: 'url-loader', 146 | options: { limit: spec.urlLoaderLimit } 147 | }] 148 | } 149 | }, 150 | { 151 | pkg: 'jade-loader', 152 | config: { 153 | test: /\.jade$/, 154 | use: ['jade-loader'] 155 | } 156 | }, 157 | { 158 | pkg: 'pug-loader', 159 | config: { 160 | test: /\.(pug|jade)$/, 161 | use: ['pug-loader'] 162 | } 163 | } 164 | ] 165 | 166 | // Add optional 167 | optionalBaseLoaders.forEach(function (item) { 168 | if (isInstalled(item.pkg)) { 169 | baseConfig.module.rules.push(item.config) 170 | } 171 | }) 172 | 173 | return baseConfig 174 | } 175 | -------------------------------------------------------------------------------- /lib/get-package.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var findRoot = require('find-root') 4 | var rootFolder = findRoot(process.cwd()) 5 | 6 | module.exports = function (pack) { 7 | var result = getPackage(pack) 8 | 9 | if (!result.version) { 10 | result.version = '0.0.0' 11 | } 12 | 13 | if (!result.name) { 14 | result.name = 'built-by-hjs-webpack' 15 | } 16 | return result 17 | } 18 | 19 | function getPackage (pack) { 20 | if (typeof pack === 'string') { 21 | // see if it's JSON 22 | try { 23 | return JSON.parse(pack) 24 | } catch (e) { 25 | // see if it's a filepath to the package.json file 26 | try { 27 | return JSON.parse(fs.readFileSync(path.resolve(pack), 'utf-8')) 28 | } catch (e) {} 29 | } 30 | } else if (pack == null) { 31 | // try to read it 32 | try { 33 | return JSON.parse(fs.readFileSync(path.join(rootFolder, 'package.json'), 'utf-8')) 34 | } catch (e) { 35 | throw new Error('Could not find package.json') 36 | } 37 | } 38 | 39 | // return the original in case 40 | // it was already an object. 41 | return pack 42 | } 43 | -------------------------------------------------------------------------------- /lib/html-plugin.js: -------------------------------------------------------------------------------- 1 | var assign = require('lodash.assign') 2 | 3 | // helper for generating default HTML 4 | function defaultHtml (incomingData) { 5 | var data = assign({ 6 | charset: 'utf-8', 7 | metaViewport: { 8 | userScalable: false 9 | }, 10 | html: '
', 11 | publicPath: '/' 12 | }, incomingData) 13 | var result = [''] 14 | var add = function () { 15 | result.push.apply(result, arguments) 16 | } 17 | 18 | if (data.lang) { 19 | add('') 20 | } 21 | add('') 22 | add('') 23 | if (data.metaViewport !== false) { 24 | var scalable = data.metaViewport.userScalable ? 'yes' : 'no' 25 | add('') 28 | } 29 | if (data.metaTags) { 30 | for (var key in data.metaTags) { 31 | add('') 32 | } 33 | } 34 | if (data.title) { 35 | add('' + data.title + '') 36 | } 37 | if (data.css) { 38 | add('') 39 | } 40 | if (data.head) { 41 | add(data.head) 42 | } 43 | add('') 44 | add('') 45 | if (data.html) { 46 | add(data.html) 47 | } 48 | add('') 49 | add('') 50 | if (data.lang) { 51 | add('') 52 | } 53 | return result.join('') 54 | } 55 | 56 | // Main export 57 | function HJSPlugin (options) { 58 | this.config = options || {} 59 | this.filename = options.filename || 'index.html' 60 | } 61 | 62 | HJSPlugin.prototype.apply = function (compiler) { 63 | var self = this 64 | var htmlFunction = this.config.html 65 | var isDev = this.config.isDev 66 | var serveCustomHtmlInDev = this.config.serveCustomHtmlInDev 67 | 68 | // let user pass `true` to use 69 | // the simple default 70 | // Same if `isDev` and `serveCustomHtmlInDev` is falsy 71 | if (htmlFunction === true || (!serveCustomHtmlInDev && isDev)) { 72 | htmlFunction = defaultHtml 73 | } else if (!htmlFunction) { 74 | return 75 | } 76 | 77 | self.compiler = compiler 78 | 79 | compiler.plugin('emit', function (compiler, callback) { 80 | // store stats on self 81 | self.stats = compiler.getStats().toJson() 82 | var context = self.getAssets() 83 | 84 | // attach default template renderer 85 | // this is useful if you want default 86 | // html but want to create multiple or 87 | // differently named HTML files 88 | context.defaultTemplate = function (opts) { 89 | var templateData = assign({}, context, opts) 90 | return defaultHtml(templateData) 91 | } 92 | 93 | // access to package info 94 | context.package = self.config.package 95 | 96 | // access to stats 97 | context.stats = self.stats 98 | 99 | // expose `isDev` flag to html function context 100 | context.isDev = self.config.isDev 101 | 102 | // handle both sync and async versions 103 | if (htmlFunction.length === 2) { 104 | htmlFunction(context, function (err, result) { 105 | if (err) throw err 106 | self.addAssets(compiler, result) 107 | callback() 108 | }) 109 | } else { 110 | self.addAssets(compiler, htmlFunction(context)) 111 | callback() 112 | } 113 | }) 114 | } 115 | 116 | // Oddly enough we have to pass in the compiler here 117 | // it's changed from when it was stored on `this` previously 118 | HJSPlugin.prototype.addAssets = function (compiler, data) { 119 | var dataType = typeof data 120 | var pages 121 | // if it's a string, we assume it's an html 122 | // string for the index file 123 | if (dataType === 'string') { 124 | pages = {} 125 | pages[this.filename] = data 126 | } else if (dataType === 'object') { 127 | pages = data 128 | } else { 129 | throw new Error('Result from `html` callback must be a string or an object') 130 | } 131 | 132 | for (var name in pages) { 133 | compiler.assets[name] = (function (asset) { 134 | return { 135 | source: function () { 136 | return asset 137 | }, 138 | size: function () { 139 | return asset.length 140 | } 141 | } 142 | }(pages[name])) 143 | } 144 | } 145 | 146 | HJSPlugin.prototype.getAssets = function () { 147 | var assets = this.assets = {} 148 | var value, chunk 149 | 150 | for (chunk in this.stats.assetsByChunkName) { 151 | value = this.stats.assetsByChunkName[chunk] 152 | 153 | // Webpack outputs an array for each chunk when using sourcemaps 154 | if (value instanceof Array) { 155 | // if we've got a CSS file add it here 156 | if (chunk === 'main' && value.length === 2) { 157 | assets.css = value[1] 158 | } 159 | 160 | // The main bundle seems like it's always the first 161 | value = value[0] 162 | } 163 | 164 | assets[chunk] = value 165 | } 166 | 167 | return assets 168 | } 169 | 170 | module.exports = HJSPlugin 171 | -------------------------------------------------------------------------------- /lib/installed-hot-loaders.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var path = require('path') 3 | 4 | function load (config) { 5 | // add hot loading clientside code 6 | config.entry.unshift( 7 | // Full path to webpack-hot-middleware so it works in npm2 and npm3 8 | path.join(path.dirname(require.resolve('webpack-hot-middleware')), 'client') 9 | ) 10 | 11 | // add dev plugins 12 | config.plugins = config.plugins.concat([ 13 | new webpack.HotModuleReplacementPlugin(), 14 | new webpack.NoEmitOnErrorsPlugin() 15 | ]) 16 | } 17 | 18 | module.exports = { 19 | load: load 20 | } 21 | -------------------------------------------------------------------------------- /lib/installed-style-loaders.js: -------------------------------------------------------------------------------- 1 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 2 | var isInstalled = require('./is-installed') 3 | 4 | var postCssOptions = {} 5 | var hasInstalledAutoprefixer = isInstalled('autoprefixer') 6 | 7 | try { 8 | // look for a config file and use its contents 9 | postCssOptions = require(process.cwd() + '/postcss.config.js') 10 | 11 | // our label says autoprefixer is included, so we should support that 12 | // plugin not being defined even if there is a config file 13 | if (hasInstalledAutoprefixer) { 14 | var isMissingAutoprefixerInPlugins = !postCssOptions.plugins || !postCssOptions.plugins.includes(function (plugin) { 15 | return plugin.postcssPlugin === 'autoprefixer' 16 | }) 17 | 18 | if (isMissingAutoprefixerInPlugins) { 19 | if (!postCssOptions.plugins) { 20 | postCssOptions.plugins = [require('autoprefixer')] 21 | } else { 22 | postCssOptions.plugins.push(require('autoprefixer')) 23 | } 24 | } 25 | } 26 | } catch (e) { 27 | postCssOptions = { 28 | plugins: hasInstalledAutoprefixer ? [require('autoprefixer')] : [] 29 | } 30 | } 31 | 32 | var postcssLoaderWithPlugins = { 33 | loader: 'postcss-loader', 34 | options: postCssOptions 35 | } 36 | 37 | var stylusLoaderWithPlugins = { 38 | loader: 'stylus-loader', 39 | options: { 40 | use: isInstalled('yeticss') ? [require('yeticss')()] : [] 41 | } 42 | } 43 | 44 | var installedLoaders = [] 45 | var baseLoaders = ['style-loader', 'css-loader', 'postcss-loader'] 46 | var baseConfig = { 47 | dev: { 48 | test: /\.css$/, 49 | use: ['style-loader', 'css-loader', postcssLoaderWithPlugins] 50 | }, 51 | production: { 52 | test: /\.css$/, 53 | use: ExtractTextPlugin.extract({ 54 | fallback: 'style-loader', 55 | use: ['css-loader', postcssLoaderWithPlugins] 56 | }) 57 | } 58 | } 59 | 60 | // All optional loader plugins are listed here 61 | // `pkg` is the npm name of the loaders 62 | // `config` contains a webpack loader config for development and production 63 | 64 | var styleLoaders = [ 65 | { 66 | pkg: 'stylus-loader', 67 | config: { 68 | dev: { 69 | test: /\.styl$/, 70 | use: ['style-loader', 'css-loader', postcssLoaderWithPlugins, stylusLoaderWithPlugins] 71 | }, 72 | production: { 73 | test: /\.styl$/, 74 | use: ExtractTextPlugin.extract({ 75 | fallback: 'style-loader', 76 | use: ['css-loader', postcssLoaderWithPlugins, stylusLoaderWithPlugins] 77 | }) 78 | } 79 | } 80 | }, 81 | { 82 | pkg: 'less-loader', 83 | config: { 84 | dev: { 85 | test: /\.less$/, 86 | use: ['style-loader', 'css-loader', postcssLoaderWithPlugins, 'less-loader'] 87 | }, 88 | production: { 89 | test: /\.less$/, 90 | use: ExtractTextPlugin.extract({ 91 | fallback: 'style-loader', 92 | use: ['css-loader', postcssLoaderWithPlugins, 'less-loader'] 93 | }) 94 | } 95 | } 96 | }, 97 | { 98 | pkg: 'sass-loader', 99 | config: { 100 | dev: { 101 | test: /\.scss$/, 102 | use: ['style-loader', 'css-loader', postcssLoaderWithPlugins, 'sass-loader'] 103 | }, 104 | production: { 105 | test: /\.scss$/, 106 | use: ExtractTextPlugin.extract({ 107 | fallback: 'style-loader', 108 | use: ['css-loader', postcssLoaderWithPlugins, 'sass-loader'] 109 | }) 110 | } 111 | } 112 | }, 113 | { 114 | pkg: 'sass-loader', 115 | config: { 116 | dev: { 117 | test: /\.sass$/, 118 | use: [ 119 | 'style-loader', 'css-loader', postcssLoaderWithPlugins, 120 | { loader: 'sass-loader', options: { indentedSyntax: true } } 121 | ] 122 | }, 123 | production: { 124 | test: /\.sass$/, 125 | use: ExtractTextPlugin.extract({ 126 | fallback: 'style-loader', 127 | use: [ 128 | 'css-loader', postcssLoaderWithPlugins, 129 | { loader: 'sass-loader', options: { indentedSyntax: true } } 130 | ] 131 | }) 132 | } 133 | } 134 | } 135 | ] 136 | 137 | // First check if any but not all of the base loaders are installed 138 | var someBaseLoadersInstalled = baseLoaders.some(isInstalled) 139 | var allBaseLoadersInstalled = baseLoaders.every(isInstalled) 140 | 141 | if (someBaseLoadersInstalled && !allBaseLoadersInstalled) { 142 | throw new Error('The following loaders must all be installed together: ' + baseLoaders.join(', ')) 143 | } else { 144 | installedLoaders.push(baseConfig) 145 | } 146 | 147 | styleLoaders 148 | .filter(function (loader) { return isInstalled(loader.pkg) }) 149 | .forEach(function (loader) { 150 | if (allBaseLoadersInstalled) { 151 | installedLoaders.push(loader.config) 152 | } else { 153 | throw new Error('The loader ' + loader.pkg + ' needs the following loaders to be installed: ' + baseLoaders.join(', ')) 154 | } 155 | }) 156 | 157 | module.exports = installedLoaders 158 | -------------------------------------------------------------------------------- /lib/is-installed.js: -------------------------------------------------------------------------------- 1 | module.exports = function (name) { 2 | try { 3 | require.resolve(name) 4 | return true 5 | } catch (e) { 6 | return false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hjs-webpack", 3 | "description": "Presets for setting up webpack with hotloading react and ES6(2015) using Babel.", 4 | "version": "9.2.0", 5 | "author": "Henrik Joreteg ", 6 | "bin": { 7 | "hjs-dev-server": "bin/hjs-dev-server.js" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/henrikjoreteg/hjs-webpack/issues" 11 | }, 12 | "dependencies": { 13 | "compression": "^1.7.1", 14 | "connect-history-api-fallback": "^1.5.0", 15 | "contains-path": "^1.0.0", 16 | "express": "^4.16.2", 17 | "extract-text-webpack-plugin": "^3.0.2", 18 | "find-root": "^1.1.0", 19 | "http-proxy-middleware": "^0.17.4", 20 | "lodash.assign": "^4.2.0", 21 | "lodash.defaults": "^4.2.0", 22 | "lodash.pick": "^4.4.0", 23 | "rimraf": "^2.6.2", 24 | "uglifyjs-webpack-plugin": "^1.1.0", 25 | "webpack": "^3.8.1", 26 | "webpack-dev-middleware": "^1.12.0", 27 | "webpack-hot-middleware": "^2.20.0" 28 | }, 29 | "devDependencies": { 30 | "git-validate": "^2.2.2", 31 | "standard": "^10.0.3" 32 | }, 33 | "homepage": "https://github.com/henrikjoreteg/hjs-webpack", 34 | "keywords": [ 35 | "config", 36 | "human javascript", 37 | "webpack" 38 | ], 39 | "license": "MIT", 40 | "main": "index.js", 41 | "pre-commit": [ 42 | "lint", 43 | "validate" 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/henrikjoreteg/hjs-webpack" 48 | }, 49 | "scripts": { 50 | "lint": "standard", 51 | "validate": "npm ls" 52 | }, 53 | "standard": { 54 | "ignore": [ 55 | "examples/*/public/*", 56 | "examples/*/node_modules/*" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /resources/hjs-webpack-localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID1DCCArwCCQC1ATCF3SR0PzANBgkqhkiG9w0BAQUFADCBqzELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEzARBgNVBAcTClRyaS1DaXRpZXMxIDAe 4 | BgNVBAoTF0hKUy1XZWJwYWNrIC8gQnJpZWZjYXNlMRowGAYDVQQLExFMb3ZlbHkg 5 | RGV2IFNlcnZlcjESMBAGA1UEAxMJbG9jYWxob3N0MSAwHgYJKoZIhvcNAQkBFhF0 6 | ZGRAdGRkc3dvcmxkLmNvbTAeFw0xNjAyMDUyMjQwMzBaFw0xNzAyMDQyMjQwMzBa 7 | MIGrMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjETMBEGA1UEBxMK 8 | VHJpLUNpdGllczEgMB4GA1UEChMXSEpTLVdlYnBhY2sgLyBCcmllZmNhc2UxGjAY 9 | BgNVBAsTEUxvdmVseSBEZXYgU2VydmVyMRIwEAYDVQQDEwlsb2NhbGhvc3QxIDAe 10 | BgkqhkiG9w0BCQEWEXRkZEB0ZGRzd29ybGQuY29tMIIBIjANBgkqhkiG9w0BAQEF 11 | AAOCAQ8AMIIBCgKCAQEA6mp5nenSuBTwVJoRvkeJwcBDAXAeWD8HNbrBGRcCfrgE 12 | HD9b7VgqUkGjYJR7CpE/B3j76pMA9QZgLrsnqYQykUPgfzEeKX6iHacKzadQTiqY 13 | 0nHX3rRafQ7Okv+qMk8YxKOBcsXIWMErGBK3BbRHlH+lKLH2O/EAorcRoBvUrZke 14 | BaoOOObySXwCcwr39G1V490BPEklz5H2pH15ibOaqAPo8u7f5vR8bMxpfqMCbW1l 15 | 1kbKO3RpayZ7G8evyJ/jgg3ZxY3QmCSFzE0u9khuTRRH29hfcLQ8Wt7gh2A1SkhH 16 | wz/d2f7uGzF/Ryd/4gSpr8BZNm05O7SzRKsfYTuAwQIDAQABMA0GCSqGSIb3DQEB 17 | BQUAA4IBAQCoKJddSANdqFxgon+97ga9jS9TRUAzOuJDnHIxJTUzk4Gt9M9hrF6M 18 | h2i6cwjq6wdJeTE8mAHnGIxzZC3HpmpDatNzj81bXKPLvoKbCRnYPng7vOKwxzvz 19 | uWBfryrXZ1yb43yUpLmTqHmaeNwO3dzxXYljD2Zb2Dq0MsEcCdeA7rGtlCeJ8EiX 20 | x4eclRXGve8TGUbMV2trFOf95lo9ktsZ+7MGi0Eo469ZaefKuo6kLdHTe+qnp5cr 21 | 8eOyE2H64i94vZiIrd3LFSLa7uJFybxlHYidkHBVP7DQ/TgZYiCljK3t8nU2IAsx 22 | OSVR0lFQQ7D1DtcPmjgpYC0iPP5p5IcM 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /resources/hjs-webpack-localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEA6mp5nenSuBTwVJoRvkeJwcBDAXAeWD8HNbrBGRcCfrgEHD9b 3 | 7VgqUkGjYJR7CpE/B3j76pMA9QZgLrsnqYQykUPgfzEeKX6iHacKzadQTiqY0nHX 4 | 3rRafQ7Okv+qMk8YxKOBcsXIWMErGBK3BbRHlH+lKLH2O/EAorcRoBvUrZkeBaoO 5 | OObySXwCcwr39G1V490BPEklz5H2pH15ibOaqAPo8u7f5vR8bMxpfqMCbW1l1kbK 6 | O3RpayZ7G8evyJ/jgg3ZxY3QmCSFzE0u9khuTRRH29hfcLQ8Wt7gh2A1SkhHwz/d 7 | 2f7uGzF/Ryd/4gSpr8BZNm05O7SzRKsfYTuAwQIDAQABAoIBAQDCuipG5Jr38eTm 8 | 0omUwEaOHZFL24BJnCn4FygbGIMpEuHPOU667fdSF1aJfy5qy9dI9NaHuRAdeuth 9 | RnUfG0MKaxFNd1bv71LqQP33clqCZF5IyushBD77DdbKQ/l1C0Gu74gz+FqCaAzN 10 | VZaV9XlUwWhIUvUSPBRkFqFcmGHmxejfC32uW+FjpYvty1INh94dmPkBh7BIRUHa 11 | NGpbeJu9ddK1OTmpN0uH/Zl6039MikC+MzuR3+3rTP5qFQhbj8MZpBwPweMAFOkQ 12 | vTatO39Qv/Ms/MkYh6yUxIYZSfoclMc7TEnRAm3XysTbpVGFzbwMslw8NHoiQ0Tu 13 | cQWRAgUlAoGBAP6dXpPS3TWXkf2eDmJKbk6tSM4R5Lr2EY89FL9ERpxbT56YKuwP 14 | Hw5oJQ3Gsq6KUoIWFd9Pp2yRdiB5p3jVmQb0q2O8wbyjLgvdAxOwfNG7c8q5+xO/ 15 | 7e824FhWBQkANluzpnHUiKGYIS+QmyGy8uHHS22lefAwW63xL1k9wdKvAoGBAOuw 16 | +PQQGaQ+uwyZQG5BI55lWRMUROmf9hMcw05d/uHlrZa9NsK5oEfJbVuADTV3r9qL 17 | z9WSbP2MxyD5Dk0mpgS6J5M4720z5QVB20U9KTpV+fvqwgbDHWSPlVFcFf/t90BX 18 | MKE7SeLPz/nDBKEhZzuDU4itlsPmGnNZG5LRLn+PAoGBAM/SuaDlK6QTzJj7ivgV 19 | ksXrURLKri0grqKBE88utofnTCzCPb6idRH4Vc5R0gT/aGtABahZxumqLYZCZj0N 20 | 87MmwEJPjr8fXnVWd615j9hNLi1Z8qNjNJ3d5+6Sf9eh8byV/ZTKCqADoYrGeKlf 21 | /Dnnxxx/RfGUNzDaPXCVJQCHAoGBAJJz1k45zuUbQblzZPDdsxdSnIXZe3S8O/jm 22 | T4nbqGyP/kyDaIqZM7pHtJc9u3d01qXzaIKqSOVIW9fmSnewQ0r+rWay4zu1GmMo 23 | 5+pfNcYU9JAP8N6o0RPSkJMMqHLuWEv3obUeFSxFu1LUVoUVL4NweDCexOcXjWoN 24 | fa++IamLAoGAD0PhU1Vm/AuqgBgdPiDo5O7aQK8nvjw0U3LSI9UY0+Z/MFPtdIFv 25 | vrfnN7j+bTYfKDMHLVjJl7bN64KVdqPjlPzjUkMHvXStrB3C7b9Z85DGfbCZ1qN4 26 | 7VGjaCS3G7lyFWHjT80/21VACEENfinmXtaM+Q9ZFoeunmFZcj8GICM= 27 | -----END RSA PRIVATE KEY----- 28 | --------------------------------------------------------------------------------