Original address: 52dachu.com/post/201606…
Because I have tried webpack, React, Redux and ES6 technology stacks in my work recently, I have concluded a set of Boilerplate so that I can start quickly and continue to optimize the next project. Corresponding project address: Webpack-react-redux-ES6-boilerplate
Project structure planning
The CSS, IMG, and JS files related to each module are put together to make it more intuitive and easier to delete modules. The test files are also kept together, so it is easy to see which modules have not written tests and which tests should be removed along with the modules.
Build | - webpack. Config. # js common configuration | -- webpack. Dev. | js # development configuration -- webpack. The js docs # # release configuration project documentation node_modules SRC # | project source code -- the conf # # config file | -- pages page directory | | - page1 | | | -- index. Js # page logic | | | -- index. SCSS # page styling | | | - img # page images | | | | -- xx. PNG | | | - __tests__ # test file | | | | -- xx. Js | | - app. HTML page # entrance | | - app. Js js | - components # # entry Component directory | | - loading | | | -- index. Js | | | -- index. SCSS | | | - __tests__ | | | | -- xx. Js | - js | | - actions | | | - index.js | | |-- __tests__ | | | |-- xx.js | |-- reducers | | |-- index.js | | |-- __tests__ | | | |-- xx.js | |-- xx.js | - CSS # public CSS directory | | -- common. SCSS public images directory | | - img # | -- xx. PNG tests # other test file package. The json READNE. The mdCopy the code
The function to be completed
-
Compile JSX, ES6, SCSS and other resources
-
Automatically import static resources into the corresponding HTML page
-
Compile and refresh the browser in real time
-
Automatically package modules according to specified modular specifications
-
Automatically adds browser kernel prefixes to CSS
-
Package and merge JS and CSS as required
-
Compress JS, CSS, and HTML
-
Picture path processing, compression, CssSprite
-
Files are named with hash for strong caching
-
Syntax checking
-
Globally replaces the specified string
-
Local interface emulates services
-
Publish to the remote machine
In view of the above functions, this Boilerplate project will be completed step by step and the key points of each step will be recorded.
The preparatory work
1. Create a project skeleton according to the previous project structure planning
$ make dir webpack-react-redux-es6-boilerplate $ cd webpack-react-redux-es6-boilerplate $ mkdir build docs src mock Tests $touch build/webpack.config.js build/webpack.dev.js build/webpack.release.Copy the code
2. Install the basic NPM packages
$ npm i webpack webpack-dev-server --save-dev
$ npm i react react-dom react-router redux react-redux redux-thunk --saveCopy the code
3. Write sample code, and the final code looks directly at boilerplate
Write the basic WebPack configuration according to the WebPack documentation, using the NODE API directly
/* webpack.config.js */ var webpack = require('webpack'); Var utils = require('./utils'); var fullPath = utils.fullPath; var pickFiles = utils.pickFiles; // Project root path var ROOT_PATH = fullPath('.. / '); Var SRC_PATH = ROOT_PATH + '/ SRC '; Var DIST_PATH = ROOT_PATH + '/dist'; Var __DEV__ = process.env.node_env! == 'production'; // conf var alias = pickFiles({ id: /(conf\/[^\/]+).js$/, pattern: SRC_PATH + '/conf/*.js' }); // components alias = Object.assign(alias, pickFiles({ id: /(components\/[^\/]+)/, pattern: SRC_PATH + '/components/*/index.js' })); // reducers alias = Object.assign(alias, pickFiles({ id: /(reducers\/[^\/]+).js/, pattern: SRC_PATH + '/js/reducers/*' })); // actions alias = Object.assign(alias, pickFiles({ id: /(actions\/[^\/]+).js/, pattern: SRC_PATH + '/js/actions/*' })); var config = { context: SRC_PATH, entry: { app: ['./pages/app.js'] }, output: { path: DIST_PATH, filename: 'js/bundle.js' }, module: {}, resolve: { alias: alias }, plugins: [ new webpack.DefinePlugin({ // http://stackoverflow.com/questions/30030031/passing-environment-dependent-variables-in-webpack "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || 'development') }) ] }; module.exports = config;Copy the code
/* webpack.dev.js */ var webpack = require('webpack'); var WebpackDevServer = require('webpack-dev-server'); var config = require('./webpack.config'); var utils = require('./utils'); var PORT = 8080; var HOST = utils.getIP(); var args = process.argv; var hot = args.indexOf('--hot') > -1; var deploy = args.indexOf('--deploy') > -1; // Static resource path in the local environment var localPublicPath = 'http://' + HOST + ':' + PORT + '/'; config.output.publicPath = localPublicPath; config.entry.app.unshift('webpack-dev-server/client? ' + localPublicPath); new WebpackDevServer(webpack(config), { hot: hot, inline: true, compress: true, stats: { chunks: false, children: false, colors: true }, // Set this as true if you want to access dev server from arbitrary url. // This is handy if you are using a html5 router. historyApiFallback: true, }).listen(PORT, HOST, function() { console.log(localPublicPath); });Copy the code
With the above configuration written, you can start building
$ node build/webpack.dev.jsCopy the code
Because JSX, ES6 and SCSS are used in the project, the corresponding loader must be added. Otherwise, the following error will be reported:
ERROR in ./src/pages/app.js
Module parse failed: /Users/xiaoyan/working/webpack-react-redux-es6-boilerplate/src/pages/app.js Unexpected token (18:6)
You may need an appropriate loader to handle this file type.Copy the code
Compile JSX, ES6, SCSS and other resources
$NPM I babel-es2015 babel-react --save-dev // Preset is recommended loader $ npm i babel-loader --save-devCopy the code
Create the.babelrc file in the project root directory:
{
"presets": ["es2015", "react"]
}Copy the code
Add the following to webpack.config.js:
Var CACHE_PATH = ROOT_PATH + '/cache'; // loaders config.module.loaders = []; . / / use the Babel compile JSX, es6 config module. The loaders. Push ({test: / \. Js $/, exclude: / node_modules /, include: [' Babel? CacheDirectory =' + CACHE_PATH]});Copy the code
Compile sass with sass-loader:
$ npm i sass-loader node-sass css-loader style-loader --save-devCopy the code
-
Css-loader is used to import CSS as a module
-
Style-loader is used to automatically add CSS to the page
Add the following to webpack.config.js:
. / / compile sass config module. Loaders. Push ({test: / \. (SCSS | CSS) $/, loaders: [' style ', 'CSS', 'sass']});Copy the code
Automatically import static resources into the corresponding HTML page
$ npm i html-webpack-plugin --save-devCopy the code
Add the following to webpack.config.js:
Var HtmlwebpackPlugin = require('html-webpack-plugin'); config.plugins.push( new HtmlwebpackPlugin({ filename: 'index.html', chunks: ['app'], template: SRC_PATH + '/pages/app.html' }) );Copy the code
At this point, the whole project is up and running
$ node build/webpack.dev.jsCopy the code
Compile and refresh the browser in real time
Once the previous configuration is complete, the project is ready to compile in real time and refresh the browser automatically. Configure hot update with react-hot-loader:
$ npm i react-hot-loader --save-devCopy the code
Since hot updates are only needed at development time, add the following code to webpack.dev.config:
If (hot === true) {config.entry.app.unshift('webpack/hot/only-dev-server'); Loaders [0].loaders [0].loaders. Unshift ('react-hot'); config.plugins.push(new webpack.HotModuleReplacementPlugin()); }Copy the code
Execute the following command and try to change the JS and CSS:
$ node build/webpack.dev.js --hotCopy the code
Automatically package modules according to specified modular specifications
Webpack supports CommonJS and AMD specifications, how to use direct view documentation
Automatically adds browser kernel prefixes to CSS
Use postcss – loader
npm i postcss-loader precss autoprefixer --save-devCopy the code
Add the following to webpack.config.js:
. / / compile sass config module. Loaders. Push ({test: / \. (SCSS | CSS) $/, loaders: [' style ', 'CSS', 'sass', 'postcss]}); // css autoprefix var precss = require('precss'); var autoprefixer = require('autoprefixer'); config.postcss = function() { return [precss, autoprefixer]; }Copy the code
Package and merge JS and CSS
Webpack packs all modules into a bundle by default and provides Code Splitting for us to split on demand. In this example we split the framework and library:
In webpack.config.js add:
config.entry.lib = [ 'react', 'react-dom', 'react-router', 'redux', 'react-redux', 'redux-thunk' ] config.output.filename = 'js/[name].js'; config.plugins.push( new webpack.optimize.CommonsChunkPlugin('lib', 'js/lib.js') ); // don't forget to add lib to the HTML page // chunks: ['app', 'lib']Copy the code
How to split CSS: Separate CSS bundle
Compress JS, CSS, HTML, PNG images
Compressed resources are best used only in a production environment
/ / compression js, CSS config. Plugins. Push (new webpack. Optimize. UglifyJsPlugin ({compress: {warnings: false}})); Var HtmlwebpackPlugin = require('html-webpack-plugin'); config.plugins.push( new HtmlwebpackPlugin({ filename: 'index.html', chunks: ['app', 'lib'], template: SRC_PATH + '/pages/app.html', minify: { collapseWhitespace: true, collapseInlineTagWhitespace: true, removeRedundantAttributes: true, removeEmptyAttributes: true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, removeComments: true } }) );Copy the code
Picture path processing, compression, CssSprite
-
Compress images using image-webpack-loader
-
Image path processing using urL-loader
$ npm i url-loader image-webpack-loader --save-devCopy the code
Add the following to webpack.config.js:
. / / image path processing, compression config module, loaders. Push ({test: / \. (? :jpg|gif|png|svg)$/, loaders: [ 'url?limit=8000&name=img/[hash].[ext]', 'image-webpack' ] });Copy the code
Sprite diagram processing: webpack_auto_sprites
Files are named with hash for strong caching
According to docs, add [hash] to the output file name
config.output.filename = 'js/[name].[hash].js';Copy the code
Local interface emulates services
$NPM install epxress --save-dev $mkdir mock && CD mock $touch app.jsCopy the code
var express = require('express'); var app = express(); All ('*', function(req, res, next) {res.header(' access-control-allow-origin ', '*'); next(); }); App.get ('/ API /test', function(req, res) {res.send({code: 200, data: 'your data'}); }); var server = app.listen(3000, function() { var host = server.address().address; var port = server.address().port; console.log('Mock server listening at http://%s:%s', host, port); });Copy the code
$node app.js & $node app.js & $node app.js &Copy the code
Publish to the remote machine
Write a deploy plugin to upload files using FTP
$ npm i ftp --save-dev
$ touch build/deploy.plugin.jsCopy the code
// build/deploy.plugin.js var Client = require('ftp'); var client = new Client(); Var __assets__ = []; Var __connected__ = false; var __conf__ = null; function uploadFile(startTime) { var file = __assets__.shift(); // Close connection without file if (! file) return client.end(); Put (file.source, file.remotePath, function(err) {var timming = date.now () - startTime; if (err) { console.log('error ', err); console.log('upload fail -', file.remotePath); } else { console.log('upload success -', file.remotePath, timming + 'ms'); If (__assets__.length === 0) {client.end(); } else { uploadFile(); }}); Function connect(conf) {if (! __connected__) { client.connect(__conf__); Client. On ('ready', function() {__connected__ = true; uploadFile(Date.now()); }); Client. on('close', function() {__connected__ = false; If (__assets__.length > 0) connect(); }); /** * [deploy description] * @param {Array} Assets Files to be deployed * file.source buffer * file.remotePath path */ function deployWithFtp(conf, assets, callback) { __conf__ = conf; __assets__ = __assets__.concat(assets); connect(); } var path = require('path'); /** * [DeployPlugin description] * @param {Array} options * option.reg * option.to */ function DeployPlugin(conf, options) { this.conf = conf; this.options = options; } DeployPlugin.prototype.apply = function(compiler) { var conf = this.conf; var options = this.options; compiler.plugin('done', function(stats) { var files = []; var assets = stats.compilation.assets; for (var name in assets) { options.map(function(cfg) { if (cfg.reg.test(name)) { files.push({ localPath: name, remotePath: path.join(cfg.to, name), source: new Buffer(assets[name].source(), 'utf-8') }); }}); } deployWithFtp(conf, files); }); }; module.exports = DeployPlugin;Copy the code
Using the plug-in written above, realize the development of local, test environment at the same time, and can automatically refresh and hot update. Add this to webpack.dev.js:
var DeployPlugin = require('./deploy.plugin'); // Whether to deploy to the test environment if (deploy === true) {config.plugins.push(new DeployPlugin({user: 'username', password: 'password', host: 'your host', keepalive: 10000000 }, [{reg: /html$/, to: '/xxx/xxx/xxx/app/views/'}]) ); }Copy the code
In this case, only the HTML files are published to the test environment, and the static resources are still used locally with Webpack-dev-server, so hot updates and auto-refresh are still available
Other publishing plug-ins:
-
sftp-webpack-plugin
-
webpack-sftp-client