Webpack&React (三) 使用Webpack

在GitHub上发现的学习webpack和react的很好的文章,原文链接

通过看前两篇文章你可能对Webpack有了一些了解,它是一个模块购建工具,可以打包你的文件.

听起来容易,实际上却不简单,我们都不想自已处理所有细节,这正是webpack要做的,接下来,将配置我们Webpack的第一个工程,并运行它在开发模式.

建立工程

Webpack依赖于Node.js. 请确让你已装安装了它,并可以在你的命令行中运行npm命令. 建立一个目录用来放置你的工程, 进入到目录中,运行npm init, 并按提示填写信息,或者全部回车使用默任设置.

mkdir kanban_app
cd kanban_app
npm init -y # -y 直接使用默认的 *package.json*, 跳过手动设置

在你工程的目录下将会生成package.json文件,你可以打开它来进一步的配置你的工程.下面会主要使用npm这个工具,但是还是建议大家学一下package.json中的另种选项用法.

安装Git

略.

安装Webpack

下一步, 我们安装Webpack, 我们使用局部安装并把它保存为工程依赖,这有利于针对每个工程,维护Webpack的版本. 执行

npm i webpack --save-dev

npm维护由它安装的可执行包的目录,你能使用npm bin命令显示这个具体的目录,很可能是.../node_modules/.bin。试一下在命令行中运行node_modules/.bin/webpack命令 或 node_modules\.bin\webpack在windows。
之后你会看到打印出的有版本信息,和一些可用的选项。

kanban_app $ node_modules/.bin/webpack
webpack 1.12.12
Usage: https://webpack.github.io/docs/cli.html

Options:
  --help, -h, -?
  --config
  --context
  --entry
...
  --display-cached-assets
  --display-reasons, --verbose, -v

Output filename not configured.

Webpack使用全局安装(安装时指定-g或-global),而不是使用它安装为项目的依赖,这样你可以直接控制运行的版本,它不依赖于单独的工程,你可以在任何地方使用它。

我们使用--save--save-dev 区分应用环境依赖和开发环境依赖,前者将安装到package.jsondependencies节点,而后者将写到devDependencies节点。这种分离使得项目依赖关系更容易理解,最后打包时将不会打包devDependencies所包含的包。

目录结构

现在的项目只有package.json看起来很low,我们将配置更多的具体的东西. 我们将实现一个小的web站点,并加载一些些JavaScript,然后使用Webpack进行构建. 要开始的话,请配置我们的目录结构如下.

  • /app
    • index.js
    • component.js
  • /build
    • index.html
  • package.json
  • webpack.config.js

我们将使用Webpack,基于/app生成bundle.js,为了做的这一点,我们应该设置一些资源和webpack.config.js.

设置资源

我们喜欢Hello world,所以我们创建一个这个模块.

app/component.js

module.exports = function () {
  var element = document.createElement('h1');

  element.innerHTML = 'Hello world';

  return element;
};

接下来,我们需要一个我们应用的入口点. 用require加载我们的组件并渲染它到一个DOM:

app/index.js

var component = require('./component');
var app = document.createElement('div');

document.body.appendChild(app);

app.appendChild(component());

我们还需要一些HTML,用来加载我们上面的最终生成的bundle.js

build/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Kanban app</title>
  </head>
  <body>
    <div id="app"></div>

    <script src="./bundle.js"></script>
  </body>
</html>

Webpack配置文件

我们需要告诉Webpack怎么处理我们刚刚建立的资源.这些需要在webpack.config.js中配置.

webpack.config.js

const path = require('path');

const PATHS = {
  app: path.join(__dirname, 'app'),
  build: path.join(__dirname, 'build')
};

module.exports = {
  // Entry accepts a path or an object of entries. We'll be using the
  // latter form given it's convenient with more complex configurations.
  entry: {
    app: PATHS.app
  },
  output: {
    path: PATHS.build,
    filename: 'bundle.js'
  }
};

上面的entry使用相对路径. 这个context字段,能用来配置绝对路径,考虑到很多地方需要绝对路径,我更喜欢使用绝对路径避免产生混淆.

我想使用path.join,但path.resolve是一个更好的选择.path.resolve等介于通过cd命令进行导航. 而path.join只是给你一个链接.详细信息请看 Node.js path API .

当我们调用 node_modules/.bin/webpack, 将会看到如下输出信息:

Hash: e7dd4bf8a294b5c93141
Version: webpack 1.12.11
Time: 734ms
    Asset   Size  Chunks             Chunk Names
bundle.js  12 kB       0  [emitted]  main
   [0] ./app/index.js 169 bytes {0} [built]
   [5] ./app/component.js 136 bytes {0} [built]
    + 4 hidden modules

它意思是构建你的输出目录,使用浏览器打开build/index.html文件查看例子.

增加构建脚本

考虑到每次执行node_modules/.bin/webpack有些麻烦,我们需要做些什么,通过npm和package.json控制程序运转.

package.json

...
"scripts": {
  "build": "webpack"
},
...

可以通过执行npm run 的方式运行脚本.尝试调用npm run build你会得到和之前相同的结果.

之所以"build": "webpack"没有配全路径,是因为npm会附加node_modules/.bin这个路径到运行时路径,即相当于配置"build": "node_modules/.bin/webpack". 还有一总可能是使用全局安装Webpack,但这样是有潜在危险的,它不利于我们对每个工程进行版本控制.

还能做的更好一些, 通过Grunt或者Gulp运行任务,可以达到相同的操作,并且是跨平台的. 但我们现在只是菜鸟,让我们keep things simple,暂时不去考虑它们.

配置开发服务器 webpack-dev-server

通过构建脚本开发你的应用程序,让人提不起劲,Webpack提供了一个更好的开发方式,通过webpack-dev-server这个运行在内存中的服务器,你在开发应用时它会自动刷新你的内容.它很像LiveReload或者Browsersync这些工具.

Webpack拥有这些工具最大的好处是模块热插拔(HMR), 总之,它提供了一总修补方式,不会完成刷新浏览器状态. 我们将会在学习React时详细讨论它.

你应该只是在开发时使用webpack-dev-server,如果是正式的应用考虑一个其它的成熟的解决方案,如Apache或Nginx.

要想开始webpack-dev-server,需要在跟目录下执行.

npm i webpack-dev-server --save-dev

就像上面, 我们在package.json中的scripts定义一个入口点.考虑到index.htmlbuild目录下面,我们配置webpack-dev-server
基于这个目录.改变我们的package.json.

package.json

"scripts": {
  "build": "webpack",
  "start": "webpack-dev-server --content-base build"
},

现在你执行npm run startnpm start,你应该会在命令行中看到这些信息:

> webpack-dev-server

http://localhost:8080/
webpack result is served from /
content is served from .../kanban_app/build
404s will fallback to /index.html

webpack: bundle is now VALID.

意思是,服务器已经运行起来了.如果在浏览器中打开http://localhost:8080/,你应该看到Hello World,如果你修改你的代码,你应该能在终端看到一些信息.问题是我们还是得需要手动刷新才能看到改动结果. 这是我们接下来需要解决的.

分离配置

作为开发设置有它自已的某些需求, 我们需要分离我们的Webpack配置文件.鉴于Webpack的配置全部是JavaScript的,所以我们有多种发式达到这个目地,至少以下的方式是可行的:

  • 管理配置到多个文件,通过--config参数指向Webpack.通过模块引入共享配置. 你可以在webpack/react-starter这篇文章中得到灵感.
  • 将配置推送到一个库,然后使用.例如: HenrikJoreteg/hjs-webpack.
  • 管理配置在单个文件使用分支.如我们触发脚本(例如: npm run test), npm 在环境变量中设置这些信息,我们可以匹配并返回我们想要的配置.

我更喜欢最后的方式,因为它让我可以知道发生了什么,非常适合小型项目.

To Keep things simple, 我已经定义了一个merge函数,连接数组和合并对象,我们马上就会知道,这在Webpack中是很方便的, 执行

npm i webpack-merge --save-dev

将它加入到工程中.

接下来,我们需要定义一些分离点到我们的配置,自定义它在每个npm脚本.例如:

webpack.config.js

...
插入开始
const merge = require('webpack-merge');
插入结束

插入开始
const TARGET = process.env.npm_lifecycle_event;
插入结束
const PATHS = {
  app: path.join(__dirname, 'app'),
  build: path.join(__dirname, 'build')
};

删除开始
module.exports = {
删除结束
插入开始
const common = {
插入结束
  // Entry accepts a path or an object of entries. We'll be using the
  // latter form given it's convenient with more complex configurations.
  entry: {
    app: PATHS.app
  },
  output: {
    path: PATHS.build,
    filename: 'bundle.js'
  }
};

插入开始
// Default configuration
if(TARGET === 'start' || !TARGET) {
  module.exports = merge(common, {});
}

if(TARGET === 'build') {
  module.exports = merge(common, {});
}
插入结束

现在我们可以进行扩展了,我们能连接到模块热插拔使浏览器刷新,这对开发是有帮助的.

配置模块热插拔(HMR)

HMR可以当我们做出改变时自动刷新浏览器, 我们想法是如果我们改变了 app/component.js,浏览器将自动刷新,同样适用于CSS的改变.

我了使它工作.我们需要连接到开发服务器,运行在内存中打包.Webpack使用基于WebSocket通信方式来达到这个目地. 我们让Webpack通过开发服务器的inline选项,来生成客户端部分,这个选项包含客户端需要的由HMR到Webpack生成的包的脚本.

webpack.config.js

...
leanpub-start-insert
const webpack = require('webpack');
leanpub-end-insert

...

if(TARGET === 'start' || !TARGET) {
leanpub-start-delete
  module.exports = merge(common, {});
leanpub-end-delete
leanpub-start-insert
  module.exports = merge(common, {
    devServer: {
      contentBase: PATHS.build,

      // Enable history API fallback so HTML5 History API based
      // routing works. This is a good default that will come
      // in handy in more complicated setups.
      historyApiFallback: true,
      hot: true,
      inline: true,
      progress: true,

      // Display only errors to reduce the amount of output.
      stats: 'errors-only',

      // Parse host and port from env so this is easy to customize.
      //
      // If you use Vagrant or Cloud9, set
      // host: process.env.HOST || '0.0.0.0';
      //
      // 0.0.0.0 is available to all network devices unlike default
      // localhost
      host: process.env.HOST,
      port: process.env.PORT
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ]
  });
leanpub-end-insert
}

...

鉴于我们在配置文件中配置了contentBase,故我们在package.json中移除它.

package.json

...
"scripts": {
  "build": "webpack"
leanpub-start-delete
  "start": "webpack-dev-server --content-base build"
leanpub-end-delete
leanpub-start-insert
  "start": "webpack-dev-server"
leanpub-end-insert
},
...

执行 npm start并转到localhost:8080,尝试一下修改app/component.js,会发现浏览器跟着刷新.注意当你修改JavaScript代码,这是硬刷新,CSS变动在更加友好的方式,可以不刷新应用.在下一章我们研究怎样在React中实现类似的事情,这将提供我们更好的开发经验.

如果你想改变默认端口,可以配置process.env.PORT || 3000.

HMR on Windows

略.

CSS 刷新

我们可以扩展这个方式对于CSS.Webpack允许我们改变CSS时不会强制完全刷新. 加载CSS到工程中,我们需要使用一对加载器.首先,调用:

npm i css-loader style-loader --save-dev

现在我们加载了需要的.我们还要确保Webpack知道它们.配置:

webpack.config.js

...

const common = {
  ...
leanpub-start-delete
  }
leanpub-end-delete
leanpub-start-insert
  },
  module: {
    loaders: [
      {
        // Test expects a RegExp! Note the slashes!
        test: /\.css$/,
        loaders: ['style', 'css'],
        // Include accepts either a path or an array of paths.
        include: PATHS.app
      }
    ]
  }
leanpub-end-insert
}

...

这个配置指的是以.css结尾的文件应该调用给定的加载器.test是JavaScript匹配样式文件的正则表达式.加载器是从右到左的顺序执行的.即先执行css-loader,然后执行style-loader. css-loader将处理我们CSS文件中的@importurl语句.style-loader在我们的JavaScript中的require语句.对CSS的预处理,像Sass和Less,并且加载它们.

设置初始CSS

我们为程序增加一点儿CSS:

app/main.css

body {
  background: cornsilk;
}

此外我们需要让Webpack知道到它.没有require指向它,Webpack将不能找到这个文件.

app/index.js

leanpub-start-insert
require('./main.css');
leanpub-end-insert

...

执行npm start,并打开你的浏览器到localhost:8080.

试着改变main.css 例如background: lime使得它看起来漂亮一些.

加入 Sourcemaps

为了提高debug的能力.我们能配置一个sourcemaps.它能让我们看到错误的准确位置.在Webpack是通过devtool来设置的.

webpack.config.js

...

if(TARGET === 'start' || !TARGET) {
  module.exports = merge(common, {
leanpub-start-insert
    devtool: 'eval-source-map',
leanpub-end-insert
    ...
  });
}

...

如果你运行npm start.Webpack将生成sourcemaps.Webpack支持多种不同的生成方式.详细请看官方文档.在上面的例子里,我们使用eval-source-map.它起初构建很慢.但是它支持快速重建和得到直实的文件.

更快速的开发指定选项,如cheap-module-eval-source-mapeval,生成低质量的sourcemaps.所有eval选项将把sourcemaps作为你JavaScript代码的一部分,因此,他们并不适合生产环境.但在开发过程中,往往是适合的用例.

有可能你需要在你的浏览器中启用sourcemaps选项.

使用npm-install-webpack-plugin避免npm install

为了避免一些输入,我们可以设置一个Webpack插件npm-install-webpack-plugin.随着我们的发展项目,它将检测到 Webpack 配置和项目文件所做的更改并为我们安装依赖项.它将自动修改package.json.

总结

在这章学习了,我们学习了使用Webpack进行构建和开发.当前的配置是为了更好的开发而不是用于生产环境.下一章我们将应用到React中.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值