React实战之webpack搭建开发工作流

1、介绍


Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

2、安装


  • 全局安装

以下的 NPM 安装方式,将使 webpack 在全局环境下可用:

npm install -g webpack

不推荐全局安装 webpack。这会将您项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中,可能会导致构建失败。

  • 本地安装

//安装最新版本
npm install --save-dev webpack
//安装特定版本
npm install --save-dev webpack@<version>

对于大多数项目,我们建议本地安装。这可以使我们在引入破坏式变更(breaking change)的依赖时,更容易分别升级项目。

  • 安装操作

首先我们创建一个目录,初始化 npm,以及在本地安装 webpack:

mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack
安装操作
执行完以后目录结构

2、Webpack使用


  • 简单使用webpack

现在我们将创建以下目录结构和内容
目录结构
src/index.js
function component() {
    var element = document.createElement('div');
    element.innerHTML = 'Hello, webpack';
    return element;
}
document.body.appendChild(component());
dist/index.html
 <html>
   <head>
     <title>Getting Started</title>
   </head>
   <body>
     <script src="bundle.js"></script>
   </body>
 </html>
执行 webpack,会将我们的脚本作为入口起点,然后输出为 bundle.js
./node_modules/.bin/webpack src/index.js dist/bundle.js
执行 webpack
在浏览器中打开 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello webpack'。
在浏览器中打开 index.html
  • 使用配置文件webpack.config.js

大多数项目会需要很复杂的设置,这就是为什么 webpack 要支持配置文件。这比在终端(terminal)中输入大量命令要高效的多,所以让我们创建一个取代以上使用 CLI 选项方式的配置文件:webpack.config.js

目录结构
webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
现在,让我们再次执行构建,通过使用我们的新配置:
./node_modules/.bin/webpack --config webpack.config.js
使用webpack.config.js
NPM 脚本(NPM Scripts)

考虑到用 CLI 这种方式来运行本地的 webpack 不是特别方便,我们可以设置一个快捷方式。在 package.json 添加一个 npm 脚本(npm script):

package.json
package.json

现在,可以使用 npm run build 命令,来替代我们之前用到的较长命令。注意,使用 npm 的 scripts,我们可以通过模块名,来引用本地安装的 npm 包,而不是写出完整路径。这是大多数基于 npm 的项目遵循的标准,允许我们直接调用 webpack,而不是去调用 ./node_modules/.bin/webpack。

现在运行以下命令,然后看看你的脚本别名是否正常运行:
npm run build
npm run build 运行结果

2、React和ES6以及JSX语法的支持


  • ES6以及JSX语法的支持

Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)

安装Babel依赖包
// npm一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react
目录结构
.babelrc
{
  "presets": ["react", "es2015"]
}
babel 在webpack.config.js 中进行配置
babel 在webpack.config.js 中进行配置
React的支持

安装 React 和 React-DOM

npm install --save react react-dom
使用React和ES6的语法更新index.js

注意这里我没有直接渲染到 document.body上,而是特别选择了 id 为 root 的 :
ReactDOM.render(<Hello />, document.getElementById('root'));
React官方不推荐将组件渲染到 document.body 上,因为这个节点很可能会改变,比如动态添加一个 <script> 标签等,这将使 React 的 DOM diff计算变得更加困难。

import ReactDOM from 'react-dom';
import React, {Component} from 'react';

class Hello extends Component{
  render() {
    return (
      <div>
        Hello, webpack
      </div>
    );
  }
}

ReactDOM.render(<Hello />, document.getElementById('root'));
在body中增加一个div标签
  <html>
   <head>
     <title>Getting Started</title>
   </head>
   <body>
        <div id='root'></div>
        <script src="bundle.js"></script>
   </body>
  </html>
运行
运行

浏览器运行效果

3、通过 loader 引入任何其他类型的文件


  • 加载 CSS

为了从 JavaScript 模块中 import 一个 CSS 文件,你需要在 module 配置中 安装并添加 style-loader 和 css-loader:

npm install --save-dev style-loader css-loader
加载css在webpack.config.js 中进行配置
加载css在webpack.config.js 中进行配置
我们尝试一下,通过在项目中添加一个新的 style.css 文件,并将其导入到我们的 index.js 中:
目录结构
src/style.css
.hello {
  color: red;
}
src/index.js
src/index.js
运行
npm run build

浏览器运行结果
CSS module

CSS modules 的技术就意在把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。Webpack从一开始就对CSS模块化提供了支持,在CSS loader中进行配置后,你所需要做的一切就是把”modules“传递到所需要的地方,然后就可以直接把CSS的类名传递到组件的代码中,且这样做只对当前组件有效,不必担心在不同的模块中使用相同的类名造成冲突。

CSS module在webpack.config.js 中进行配置
CSS module在webpack.config.js 中进行配置
src/index.js
屏幕快照 2017-09-06 10.48.17.png
运行
npm run build

浏览器运行结果
  • 加载图片

使用 file-loader,我们可以轻松地将这些内容混合到 CSS 中:
npm install --save-dev file-loader
加载图片在webpack.config.js 中进行配置
加载图片在webpack.config.js 中进行配置
增加一张test.png图片
目录结构
src/style.css
src/style.css
运行
npm run build

浏览器运行结果

4、webpack plugin


  • HtmlWebpackPlugin

作用

1、html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
2、可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口

安装
npm install --save-dev html-webpack-plugin
创建src/index.tmpl.html,并删除dist/index.html
目录结构
src/index.tmpl.html
  <html>
   <head>
     <title>Getting Started</title>
   </head>
   <body>
        <div id='root'></div>
   </body>
  </html>
webpack.config.js配置
webpack.config.js配置
运行
npm run build

dist目录下生成了index.html

浏览器运行index.html
了解更多 HtmlWebpackPlugin 插件

如果你想要了解更多 HtmlWebpackPlugin 插件提供的全部功能和选项,那么你就应该多多熟悉HtmlWebpackPlugin 仓库。
你还可以看一下 html-webpack-template,除了默认模板之外,还提供了一些额外的功能。

  • clean-webpack-plugin

你可能已经注意到,由于过去的指南和代码示例遗留下来,导致我们的 /dist 文件夹相当杂乱。webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。

通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法,因此只会生成用到的文件。让我们完成这个需求。

安装
npm install clean-webpack-plugin --save-dev
webpack.config.js配置
webpack.config.js配置
现在执行 npm run build,再检查 /dist 文件夹。如果一切顺利,你现在应该不会再看到旧的文件,只有构建后生成的文件!

5、开发常用配置


  • webpack-dev-server

webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。

安装
npm install --save-dev webpack-dev-server
webpack.config.js配置
webpack.config.js配置

localhost:8080 下建立服务,并启用gzip压缩

package.json配置npm script
package.json配置npm script
运行
npm start

自动打开浏览器并修改自动刷新
  • source map

介绍

当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到 bundle.js。这并通常没有太多帮助,因为你可能需要准确地知道错误来自于哪个源文件。

为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。

devtool选项
以下选项是开发的理想选择:

eval- 每个模块都用eval()和执行//@ sourceURL。这很快。主要的缺点是它不会正确显示行号,因为它被映射到折叠代码而不是原始代码(没有来自加载器的源地图)。

eval-source-map- 每个模块都执行,eval()并将SourceMap作为DataUrl添加到eval()。最初它很慢,但它提供了快速的重建速度,并产生真实的文件。行号被正确映射,因为它被映射到原始代码。它产生最好的质量SourceMaps进行开发。

cheap-eval-source-map- 类似于eval-source-map每个模块都执行eval()。它是“便宜的”,因为它没有列映射,它只映射行号。它忽略来自Loader的SourceMaps,并且仅显示与devtool类似的折叠代码eval。

cheap-module-eval-source-map- cheap-eval-source-map但是,与此类似,处理来自加载程序的源地图以获得更好的结果。然而,Loader Source Maps简化为每行一个映射。

对小到中型的项目中,eval-source-map是一个很好的选项,再次强调你只应该开发阶段使用它。
cheap-module-eval-source-map方法构建速度更快,但是不利于调试,推荐在大型项目考虑时间成本时使用。
webpack.config.js 配置
屏幕快照 2017-09-06 14.38.07.png
  • 代码分离及优化

介绍
  • OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
  • UglifyJsPlugin:压缩JS代码;
  • ExtractTextPlugin:用于将 CSS 从主应用程序中分离
安装

OccurenceOrder 和 UglifyJS plugins 都是内置插件,你需要做的只是安装其它非内置插件

npm install --save-dev extract-text-webpack-plugin
webpack.config.js配置
webpack.config.js配置
运行
npm start

运行后将css文件分离到style.css文件中


将css文件分离到style.css文件中

运行后js文件被压缩


运行后js文件被压缩
  • 缓存

介绍

通过使用 output.filename 进行文件名替换,可以确保浏览器获取到修改后的文件。[hash] 替换可以用于在文件名中包含一个构建相关(build-specific)的 hash,但是更好的方式是使用 [chunkhash] 替换,在文件名中包含一个 chunk 相关(chunk-specific)的哈希。

webpack.config.js 配置
webpack.config.js 配置
  • 生产环境构建

现在,我们把 scripts 重新指向到新配置。我们将 npm start 定义为开发环境脚本,并在其中使用 webpack-dev-server,将 npm run build 定义为生产环境脚本

创建webpack.config.prod.js文件
创建webpack.config.prod.js文件
webpack.config.prod.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].[chunkhash].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [{
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: "style-loader",
                    use: {
                        loader: "css-loader",
                        options: {
                            modules: true,
                            minimize: true
                        }
                    }
                })
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin("[name].[contenthash].css"),
        new CleanWebpackPlugin([__dirname + "/dist"]),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin(),
        new HtmlWebpackPlugin({
            template: __dirname + "/src/index.tmpl.html",//new 一个这个插件的实例,并传入相关的参数
            minify: {
                removeComments: true, //是否去掉注释
                collapseWhitespace: true, //是否去掉空格
                minifyJS: true, //是否压缩html里的js(使用uglify-js进行的压缩)
                minifyCSS: true, //是否压缩html里的css(使用clean-css进行的压缩)
            },
        })
    ],
};
webpack.config.js去除一些配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].[chunkhash].js',
        path: path.resolve(__dirname, 'dist')
    },
    devtool: 'eval-source-map',
    module: {
        rules: [{
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + "/src/index.tmpl.html",//new 一个这个插件的实例,并传入相关的参数
            minify: {
                removeComments: true, //是否去掉注释
                collapseWhitespace: true, //是否去掉空格
                minifyJS: true, //是否压缩html里的js(使用uglify-js进行的压缩)
                minifyCSS: true, //是否压缩html里的css(使用clean-css进行的压缩)
            },
        })
    ],
    devServer: {
        //一切服务都启用gzip 压缩:
        compress: true,
        port: 8080
    },
};
package.json配置
package.json配置

最后


本篇文章将涵盖webpack安装、使用、plugin、开发中常用的配置、缓存等等,我在边写边跟着构建,并将每一步构建生成git记录,代码传送门,如果有疑问的地方欢迎在下面评论或者私信给我。
如果您觉得有帮助,请给我点个👍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,478评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,825评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,482评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,726评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,633评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,018评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,168评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,320评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,264评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,288评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,995评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,587评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,909评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,284评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,862评论 2 339

推荐阅读更多精彩内容