├── .gitignore
├── .jshintrc
├── LICENSE
├── README.md
├── app
├── assets
│ ├── datas
│ │ └── data.json
│ ├── images
│ │ └── webpack.png
│ └── styles
│ │ ├── _main.scss
│ │ └── utils
│ │ ├── _global.scss
│ │ └── _reset.scss
├── lib
│ ├── components
│ │ ├── Header
│ │ │ ├── Header.jsx
│ │ │ └── Header.scss
│ │ └── ShowData
│ │ │ ├── ShowData.jsx
│ │ │ └── ShowData.scss
│ └── utils
│ │ └── utils.js
└── views
│ ├── Main1
│ ├── Main1.html
│ ├── Main1.jsx
│ └── Main1.scss
│ └── Main2
│ ├── Main2.html
│ └── Main2.jsx
├── dist
├── img
│ └── webpack.png
├── js
│ ├── Main1.bundle.js
│ ├── Main1.bundle.js.map
│ ├── Main2.bundle.js
│ ├── Main2.bundle.js.map
│ ├── commons.js
│ └── commons.js.map
└── views
│ └── Main1.html
├── package.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .cache
3 | *.log
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "browser": true,
3 | "node": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "expr": true,
8 | "esnext": true,
9 | "immed": true,
10 | "indent": 2,
11 | "maxlen": 200,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "undef": true,
16 | "strict": true,
17 | "globals": {
18 | "reqwest": true,
19 | "define": true,
20 | "module": true,
21 | "React": true,
22 | "ReactDOM": true,
23 | "ReactRouter": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, TongchengQiu
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | title: webpack-best-practice-最佳实践-部署生产
2 | date: 2015-12-02 10:56:39
3 | tags:
4 | - webpack
5 | - FE
6 | - 前端
7 | - 构建工具
8 | categories: [webpack,FE,前端,构建工具]
9 | ---
10 | # 前言
11 | 最近一段时间在项目中使用了webpack和React来开发,总之来说也是遇到了许多坑,webpack毕竟还是比较新的技术,而且也很难有一个很好的构建案例来适应所有的项目,总之,在看了许多项目demo和官方文档以及官方推荐的tutorials之后,也算是自己总结出的一套最佳实践吧。
12 | ## 代码
13 | 代码可以在我的[Github](https://github.com/TongchengQiu/webpack-best-practice)上。
14 | [可以戳这里~~](https://github.com/TongchengQiu/webpack-best-practice)。
15 | # package.json 命令配置
16 | 既然是需要用到的是实际项目的构建,那么必然就要考虑开发环境和生产环境下的配置项了:
17 | ```
18 | // package.json
19 | {
20 | // ...
21 | "scripts": {
22 | "build": "webpack --progress --colors --watch",
23 | "watch": "webpack-dev-server --hot --progress --colors",
24 | "dist": "NODE_ENV=production webpack --progress --colors"
25 | },
26 | // ...
27 | }
28 | ```
29 |
30 | 可以在目录下执行 `npm run build` , `npm run watch` , `npm run dist`
31 | 解释一下:
32 | + build 是在我们开发环境下执行的构建命令;
33 | + watch 也是在开发环境下执行,但是加了webpack最强大的功能--搭建静态服务器和热插拔功能(这个在后面介绍;
34 | + dist 是项目在要部署到生产环境时打包发布。
35 |
36 | dist 里面的``NODE_ENV=production``是声明了当前执行的环境是production-生产环境
37 |
38 | 后面跟着几个命令:
39 | + --colors 输出的结果带彩色
40 | + --progress 输出进度显示
41 | + --watch 动态实时监测依赖文件变化并且更新
42 | + --hot 是热插拔
43 | + --display-error-details 错误的时候显示更多详细错误信息
44 | + --display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
45 | + -w 动态实时监测依赖文件变化并且更新
46 | + -d 提供sorcemap
47 | + -p 对打包文件进行压缩
48 |
49 | # 目录结构
50 | 现在前端模块化的趋势导致目录结构也发生了很大的改变和争议,这只是我自己用到的一种形式,可以参考。
51 | ```
52 | .
53 | ├── app #开发目录
54 | | ├──assets #存放静态资源
55 | | | ├──datas #存放数据 json 文件
56 | | | ├──images #存放图片资源文件
57 | | | └──styles #存放全局sass变量文件和reset文件
58 | | ├──lib
59 | | | ├──components #存放数据 模块组件 文件
60 | | | | └──Header
61 | | | | ├──Header.jsx
62 | | | | └──Header.scss
63 | | | |
64 | | | └──utils #存放utils工具函数文件
65 | | |
66 | | └──views
67 | | ├──Index #入口文件
68 | | | ├──Index.html #html文件
69 | | | ├──Index.jsx
70 | | | └──Index.scss
71 | | └──Index2
72 | ├── dist #发布目录
73 | ├── node_modules #包文件夹
74 | ├── .gitignore
75 | ├── .jshintrc
76 | ├── webpack.config.js #webpack配置文件
77 | └── package.json
78 | ```
79 | 具体可以到Github上看demo。
80 | # webpack.config.js
81 | ## 引入包
82 | ```
83 | var webpack = require('webpack');
84 | var path = require('path');
85 | var fs = require('fs');
86 | ```
87 | 这个毋庸置疑吧。
88 | ## 判断是否是在当前生产环境
89 | 定义函数判断是否是在当前生产环境,这个很重要,一位开发环境和生产环境配置上有一些区别
90 | ```
91 | var isProduction = function () {
92 | return process.env.NODE_ENV === 'production';
93 | };
94 | ```
95 | ## 声明文件夹
96 | ```
97 | // 定义输出文件夹
98 | var outputDir = './dist';
99 | // 定义开发文件夹
100 | var entryPath = './app/views';
101 | ```
102 | ## 定义插件
103 | ```
104 | var plugins = [
105 | new webpack.optimize.CommonsChunkPlugin({
106 | name: 'commons',
107 | filename: 'js/commons.js',
108 | }),
109 | new webpack.ProvidePlugin({
110 | React: 'react',
111 | ReactDOM: 'react-dom',
112 | reqwest: 'reqwest',
113 | }),
114 | ];
115 | if( isProduction() ) {
116 | plugins.push(
117 | new webpack.optimize.UglifyJsPlugin({
118 | test: /(\.jsx|\.js)$/,
119 | compress: {
120 | warnings: false
121 | },
122 | })
123 | );
124 | }
125 | ```
126 | 1. CommonsChunkPlugin 插件可以打包所有文件的共用部分生产一个commons.js文件。
127 | 2. ProvidePlugin 插件可以定义一个共用的入口,比如 下面加的 React ,他会在每个文件自动require了react,所以你在文件中不需要 require('react'),也可以使用 React。
128 | 3. 如果是在生产环境下,则加入插件 UglifyJsPlugin ,执行代码压缩,并且去除 warnings。
129 |
130 | ## 自动遍历多文件入口
131 | ```
132 | var entris = fs.readdirSync(entryPath).reduce(function (o, filename) {
133 | !/\./.test(filename) &&
134 | (o[filename] = './' + path.join(entryPath, filename, filename + '.jsx'));
135 | return o;
136 | }, {}
137 | );
138 | ```
139 | 函数会自动遍历开发的入口文件夹下面的文件,然后一一生产入口并且返回一个对象--入口。
140 | ## 如果在这一步不需要多页面多入口
141 | 那么可以使用[html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin)插件,它可以自动为入口生成一个html文件,配置如下:
142 | ```
143 | var HtmlWebpackPlugin = require('html-webpack-plugin');
144 | plugins.push(new HtmlWebpackPlugin({
145 | title: 'index',
146 | filename: outputDir+'/index.html', #生成html的位置
147 | inject: 'body', #插入script在body标签里
148 | }));
149 | ```
150 | entry 就可以自定义一个入口就够了
151 | ## config的具体配置
152 | ```
153 | var config = {
154 | target: 'web',
155 | cache: true,
156 | entry: entris,
157 | output: {
158 | path: outputDir,
159 | filename: 'js/[name].bundle.js',
160 | publicPath: isProduction()? 'http://******' : 'http://localhost:3000',
161 | },
162 | module: {
163 | loaders: [
164 | {
165 | test: /(\.jsx|\.js)$/,
166 | loaders: ['babel?presets[]=es2015&presets[]=react'],
167 | exclude: /node_modules/
168 | },
169 | {
170 | test: /\.scss$/,
171 | loaders: ['style', 'css?root='+__dirname, 'resolve-url', 'sass']
172 | },
173 | {
174 | test: /\.json$/,
175 | loader: 'json',
176 | },
177 | {
178 | test: /\.(jpe?g|png|gif|svg)$/,
179 | loader: 'url?limit=1024&name=img/[name].[ext]'
180 | },
181 | {
182 | test: /\.(woff2?|otf|eot|svg|ttf)$/i,
183 | loader: 'url?name=fonts/[name].[ext]'
184 | },
185 | {
186 | test: /\.html$/,
187 | loader: 'file?name=views/[name].[ext]'
188 | },
189 | ]
190 | },
191 | plugins: plugins,
192 | resolve: {
193 | extensions: ['', '.js', 'jsx'],
194 | },
195 | devtool: isProduction()?null:'source-map',
196 | };
197 | ```
198 | 这里来一一说明:
199 | ### 对于output
200 | path和filename都不用多说了,path是生成文件的存放目录,filename是文件名,当然可以在前面加上目录位置。
201 | 这里提醒一下,filename 的相对路径就是 path了,并且下面 静态文件生成的filename也是相对于这里的path的,比如 image 和 html。
202 | publicPath 的话是打包的时候生成的文件链接,比如 图片 资源,
203 | 如果是在生产环境当然是用服务器地址,如果是开发环境就是用本地静态服务器的地址。
204 | ### module loaders 打包加载的处理器
205 | 可以不用夹 loader了 比如 原来 url-loader 现在 url
206 | #### js/jsx
207 | ```
208 | {
209 | test: /(\.jsx|\.js)$/,
210 | loaders: ['babel?presets[]=es2015&presets[]=react'],
211 | exclude: /node_modules/
212 | },
213 | ```
214 | 对于js文件和jsx文件用了babel来处理,这里注意一下,最新版本的babel吧es2015和react的处理分开了,所有要这么写。
215 | ### 处理scss文件
216 | ```
217 | {
218 | test: /\.scss$/,
219 | loaders: ['style', 'css?root='+__dirname, 'resolve-url', 'sass']
220 | },
221 | ```
222 | 这里用了sass、css、style的loader这不用多说了。
223 | 那么root和resolve-url是怎么回事呢,root是定义了scss文件里面声明的url地址是相对于根目录的,然后resolve-url回去相对解析这个路径,而不用require去获取,比如
224 | ```
225 | background: url('./assets/images/webpack.png');
226 | ```
227 | 这样就可以加载到``./assets/images/webpack.png``这个文件,而不用使用相对路径和require
228 | ### 处理json文件
229 | ```
230 | {
231 | test: /\.json$/,
232 | loader: 'json',
233 | },
234 | ```
235 | 对于json文件,可以自动请求该模块并且打包。
236 | ### 处理 图片 字体 资源文件
237 | ```
238 | {
239 | test: /\.(jpe?g|png|gif|svg)$/,
240 | loader: 'url?limit=1024&name=img/[name].[ext]'
241 | },
242 | {
243 | test: /\.(woff2?|otf|eot|svg|ttf)$/i,
244 | loader: 'url?name=fonts/[name].[ext]'
245 | },
246 | ```
247 | 这里使用了 url 这个loader,但是url依赖 file-loader,它是对file-loader的二次封装。
248 | 在请求图片的时候如果文件大小小于 1024k ,使用内联 base64 URLs,否则会自动导入到name所声明的目录,这里是相对之前声明的 outputDir 路径。
249 | 字体资源也是一样。
250 | ### 处理html文件
251 | ```
252 | {
253 | test: /\.html$/,
254 | loader: 'file?name=views/[name].[ext]'
255 | },
256 | ```
257 | 在多页面的项目中需要,可以自动吧html文件导入到指定的生产文件夹下。
258 | ## resolve
259 | ```
260 | resolve: {
261 | extensions: ['', '.js', 'jsx'],
262 | },
263 | ```
264 | 是可以忽略的文件后缀名,比如可以直接``require('Header');``而不用加.jsx。
265 | ## devtool
266 | ```
267 | devtool: isProduction()?null:'source-map',
268 | ```
269 | 规定了在开发环境下才使用 source-map。
270 |
271 | # 疑问
272 | 目前为止,对于多页面项目还是没有找到一个很好的方案去构建自动化。
273 |
--------------------------------------------------------------------------------
/app/assets/datas/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "main1": "1",
3 | "main2": {
4 | "main21": "21",
5 | "main22": "22"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/assets/images/webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TongchengQiu/webpack-best-practice/51375dd30f52a4e76ea3862be8717832cf10b80b/app/assets/images/webpack.png
--------------------------------------------------------------------------------
/app/assets/styles/_main.scss:
--------------------------------------------------------------------------------
1 | @import './utils/reset';
2 | @import './utils/global';
3 |
--------------------------------------------------------------------------------
/app/assets/styles/utils/_global.scss:
--------------------------------------------------------------------------------
1 | a {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/styles/utils/_reset.scss:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | }
21 | article, aside, details, figcaption, figure,
22 | footer, header, hgroup, menu, nav, section {
23 | display: block;
24 | }
25 | body {
26 | line-height: 1;
27 | }
28 | ol, ul {
29 | list-style: none;
30 | }
31 | blockquote, q {
32 | quotes: none;
33 | }
34 | blockquote:before, blockquote:after,
35 | q:before, q:after {
36 | content: '';
37 | content: none;
38 | }
39 | table {
40 | border-collapse: collapse;
41 | border-spacing: 0;
42 | }
43 |
--------------------------------------------------------------------------------
/app/lib/components/Header/Header.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | require('./Header.scss');
3 |
4 | var Header = React.createClass({
5 | render: function() {
6 | return (
7 |