使用 webpack + react + redux + es6 开发组件化前端项目
来源:互联网 发布:游戏主机历史 知乎 编辑:程序博客网 时间:2024/05/17 10:42
使用 webpack + react + redux + es6 开发组件化前端项目
作者:xiebaochun
因为最近在工作中尝试了 webpack、react、redux、es6 技术栈,所以总结出了一套 boilerplate,以便下次做项目时可以快速开始,并进行持续优化。
项目结构规划
每个模块相关的 css、img、js 文件都放在一起,比较直观,删除模块时也会方便许多。测试文件也同样放在一起,哪些模块有没有写测试,哪些测试应该一起随模块删除,一目了然。
build|-- webpack.config.js # 公共配置|-- webpack.dev.js # 开发配置|-- webpack.release.js # 发布配置docs # 项目文档node_modules src # 项目源码|-- conf # 配置文件|-- pages # 页面目录| |-- page1 | | |-- index.js # 页面逻辑| | |-- index.scss # 页面样式| | |-- img # 页面图片| | | |-- xx.png | | |-- __tests__ # 测试文件| | | |-- xx.js| |-- app.html # 入口页| |-- app.js # 入口JS|-- components # 组件目录| |-- loading| | |-- index.js| | |-- index.scss| | |-- __tests__ | | | |-- xx.js|-- js| |-- actions| | |-- index.js| | |-- __tests__ | | | |-- xx.js| |-- reducers | | |-- index.js| | |-- __tests__ | | | |-- xx.js| |-- xx.js |-- css # 公共CSS目录| |-- common.scss|-- img # 公共图片目录| |-- xx.pngtests # 其他测试文件package.json READNE.md
要完成的功能
- 编译 jsx、es6、scss 等资源
- 自动引入静态资源到相应 html 页面
- 实时编译和刷新浏览器
- 按指定模块化规范自动包装模块
- 自动给 css 添加浏览器内核前缀
- 按需打包合并 js、css
- 压缩 js、css、html
- 图片路径处理、压缩、CssSprite
- 对文件使用 hash 命名,做强缓存
- 语法检查
- 全局替换指定字符串
- 本地接口模拟服务
- 发布到远端机
针对以上的几点功能,接下来将一步一步的来完成这个 boilerplate 项目, 并记录下每一步的要点。
准备工作
1、根据前面的项目结构规划创建项目骨架
$ 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.js// 创建 package.json$ npm init$ ...
2、安装最基本的几个 npm 包
$ npm i webpack webpack-dev-server --save-dev$ npm i react react-dom react-router redux react-redux redux-thunk --save
3、编写示例代码,最终代码直接查看 boilerplate
4、根据 webpack 文档编写最基本的 webpack 配置,直接使用 NODE API 的方式
/* webpack.config.js */var webpack = require("webpack");// 辅助函数var utils = require("./utils");var fullPath = utils.fullPath;var pickFiles = utils.pickFiles;// 项目根路径var ROOT_PATH = fullPath("../");// 项目源码路径var SRC_PATH = ROOT_PATH + "/src";// 产出路径var DIST_PATH = ROOT_PATH + "/dist";// 是否是开发环境var __DEV__ = process.env.NODE_ENV !== "production";// confvar alias = pickFiles({ id: /(conf\/[^\/]+).js$/, pattern: SRC_PATH + "/conf/*.js"});// componentsalias = Object.assign(alias, pickFiles({ id: /(components\/[^\/]+)/, pattern: SRC_PATH + "/components/*/index.js"}));// reducersalias = Object.assign(alias, pickFiles({ id: /(reducers\/[^\/]+).js/, pattern: SRC_PATH + "/js/reducers/*"}));// actionsalias = 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;
/* 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;// 本地环境静态资源路径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);});
上面的配置写好后就可以开始构建了
$ node build/webpack.dev.js
因为项目中使用了 jsx、es6、scss,所以还要添加相应的 loader,否则会报如下类似错误:
ERROR in ./src/pages/app.jsModule 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.
编译 jsx、es6、scss 等资源
- 使用 bael 和 babel-loader 编译 jsx、es6
- 安装插件: babel-preset-es2015 用于解析
es6
- 安装插件:babel-preset-react 用于解析
jsx
// 首先需要安装 babel $ npm i babel-core --save-dev// 安装插件 $ npm i babel-preset-es2015 babel-preset-react --save-dev// 安装 loader$ npm i babel-loader --save-dev
在项目根目录创建 .babelrc
文件:
{ "presets": ["es2015", "react"]}
在 webpack.config.js 里添加:
// 使用缓存var CACHE_PATH = ROOT_PATH + "/cache";// loadersconfig.module.loaders = [];// 使用 babel 编译 jsx、es6config.module.loaders.push({ test: /\.js$/, exclude: /node_modules/, include: SRC_PATH, // 这里使用 loaders ,因为后面还需要添加 loader loaders: ["babel?cacheDirectory=" + CACHE_PATH]});
接下来使用 sass-loader 编译 sass:
$ npm i sass-loader node-sass css-loader style-loader --save-dev
- css-loader 用于将 css 当做模块一样来
import
- style-loader 用于自动将 css 添加到页面
在 webpack.config.js 里添加:
// 编译 sassconfig.module.loaders.push({ test: /\.(scss|css)$/, loaders: ["style", "css", "sass"]});
自动引入静态资源到相应 html 页面
- 使用 html-webpack-plugin
$ npm i html-webpack-plugin --save-dev
在 webpack.config.js 里添加:
// html 页面var HtmlwebpackPlugin = require("html-webpack-plugin");config.plugins.push( new HtmlwebpackPlugin({ filename: "index.html", chunks: ["app"], template: SRC_PATH + "/pages/app.html" }));
至此,整个项目就可以正常跑起来了
$ node build/webpack.dev.js
实时编译和刷新浏览器
完成前面的配置后,项目就已经可以实时编译和自动刷新浏览器了。接下来就配置下热更新,使用 react-hot-loader:
$ npm i react-hot-loader --save-dev
因为热更新只需要在开发时使用,所以在 webpack.dev.config 里添加如下代码:
// 开启热替换相关设置if (hot === true) { config.entry.app.unshift("webpack/hot/only-dev-server"); // 注意这里 loaders[0] 是处理 .js 文件的 loader config.module.loaders[0].loaders.unshift("react-hot"); config.plugins.push(new webpack.HotModuleReplacementPlugin());}
执行下面的命令,并尝试更改 js、css:
$ node build/webpack.dev.js --hot
按指定模块化规范自动包装模块
webpack 支持 CommonJS、AMD 规范,具体如何使用直接查看文档
自动给 css 添加浏览器内核前缀
使用 postcss-loader
npm i postcss-loader precss autoprefixer --save-dev
在 webpack.config.js 里添加:
// 编译 sassconfig.module.loaders.push({ test: /\.(scss|css)$/, loaders: ["style", "css", "sass", "postcss"]});// css autoprefixvar precss = require("precss");var autoprefixer = require("autoprefixer");config.postcss = function() { return [precss, autoprefixer];}
打包合并 js、css
webpack 默认将所有模块都打包成一个 bundle,并提供了 Code Splitting 功能便于我们按需拆分。在这个例子里我们把框架和库都拆分出来:
在 webpack.config.js 添加:
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"));// 别忘了将 lib 添加到 html 页面// chunks: ["app", "lib"]
如何拆分 CSS:separate css bundle
压缩 js、css、html、png 图片
压缩资源最好只在生产环境时使用
// 压缩 js、cssconfig.plugins.push( new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }));// 压缩 html// html 页面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 } }));
图片路径处理、压缩、CssSprite
- 压缩图片使用 image-webpack-loader
- 图片路径处理使用 url-loader
$ npm i url-loader image-webpack-loader --save-dev
在 webpack.config.js 里添加:
// 图片路径处理,压缩config.module.loaders.push({ test: /\.(?:jpg|gif|png|svg)$/, loaders: [ "url?limit=8000&name=img/[hash].[ext]", "image-webpack" ]});
雪碧图处理:webpack_auto_sprites
对文件使用 hash 命名,做强缓存
根据 docs,在产出文件命名中加上 [hash]
config.output.filename = "js/[name].[hash].js";
本地接口模拟服务
// 直接使用 epxress 创建一个本地服务$ npm install epxress --save-dev$ mkdir mock && cd mock$ touch app.js
var express = require("express");var app = express();// 设置跨域访问,方便开发app.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);});
// 启动服务,如果用 PM2 管理会更方便,增加接口不用自己手动重启服务$ node app.js &
发布到远端机
写一个 deploy 插件,使用 ftp 上传文件
$ npm i ftp --save-dev$ touch build/deploy.plugin.js
// build/deploy.plugin.jsvar Client = require("ftp");var client = new Client();// 待上传的文件var __assets__ = [];// 是否已连接var __connected__ = false;var __conf__ = null;function uploadFile(startTime) { var file = __assets__.shift(); // 没有文件就关闭连接 if (!file) return client.end(); // 开始上传 client.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 待 deploy 的文件 * 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;
运用上面写的插件,实现同时在本地、测试环境开发,并能自动刷新和热更新。在 webpack.dev.js 里添加:
var DeployPlugin = require("./deploy.plugin");// 是否发布到测试环境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/"}]) );}
在这个例子里,只将 html 文件发布到测试环境,静态资源还是使用的本地的webpack-dev-server,所以热更新、自动刷新还是可以正常使用
- sftp-webpack-plugin
- webpack-sftp-client
- 使用 webpack + react + redux + es6 开发组件化前端项目
- 使用 webpack + react + redux + es6 开发组件化前端项目
- 使用 webpack + react + redux + es6 开发组件化前端项目
- 使用 webpack + react + redux + es6 开发组件化前端项目
- 使用 webpack + react + redux + es6 开发组件化前端项目
- webpack + react + redux + es6 开发组件化前端项目
- react,redux,webpack前端项目
- 使用 Webpack 和 ES6 进行 React 开发
- 使用webpack搭建react ES6开发环境
- Spring集成React用来开发前端----maven项目中用webpack打包react相关组件
- 基于react+redux+webpack的前端框架
- React-redux-webpack项目总结之用到的Es6基本语法
- 前端学习 | 使用webpack构建React项目
- react+redux 前端开发流程
- react+redux+webpack移动端项目总结
- react-redux-webpack-express开发环境搭建
- ES6+React+Webpack初步构建项目流程
- webpack+react+es6 嵌入已有项目
- Codeforces Round #419 (Div. 2)
- 先来看看什么是Lambdas
- 面试官员提出:148个资源让你成为CSS专家(上)
- Java面向对象的理解
- C++设计模式:简单工厂模式
- 使用 webpack + react + redux + es6 开发组件化前端项目
- Android中用GifView显示Gif动画及Gifview简介
- 关于多用户时hadoop的权限问题
- 锋利的jQuery读书笔记-第11章 jQuery性能优化和技巧
- Web自动化测试-Protractor基础(二)
- laravel框架中vagrant常用命令
- 手把手教你在github上搭建自己的代码仓库
- 常见的路由表生成算法
- 珍惜自己有限的时间和经历