16

作者:Nicolas (沪江Web前端)
本文为原创文章,转载请注明作者及出处
本文的 webpack 代码示例根据 webpack 2.7.0 编写,并在 Mac 上正常运行。

去年一篇《在 2016 年学 JavaScript 是一种什么样的体验?》吓坏了很多想要入行新同学和入行很久的老司机,感觉一下子前端世界已经看不懂了,做个页面要那么麻烦?当然如果你只是想要一个简单的静态页面,这么玩儿就是杀鸡用牛刀了。但如果你准备开发一个 Web App,之后会不断的迭代,有一个舒适的开发环境是及其重要的,那么底怎么样的环境才会是舒适愉悦的呢?

比如这样的一个环境:资源依赖可以安装并模块化引用、可以使用很酷的 ES6 语法、可以使用 SASS 预处理器写 CSS、代码可实时更新而不用一遍遍的手动刷新页面,这样的开发环境你会不会觉得很爽!好,我们这就来配置一个这样的环境!

基础环境

首先,你需要一个 Node.js,然后 NPM 也会随着 Node.js 一起装上。

什么是 NPM ?简单的说 NPM 是用来下载安装 Node.js 的第三方工具包的一个管理器。当然,现在也可以安装浏览器中使用的包。提到包管理器,就不得不说下 Bower,Bower 之前一直是前端库管理工具,一开始 NPM 只能发布和安装 Node.js 的包,所以 Bower 盛行一时,随着 CommonJS 的普及,以及 UMD 规范的出现,让 NPM 安装前端浏览器 js 包成为了可能,随着 NPM 生态的成熟,Bower 也就慢慢被人淡忘了~

Node.js 安装完成后,可以执行以下命令验证安装是否成功:

$ node -v
v6.11.0

$ npm -v
3.10.10

别急,Node.js 的部分还没完,国内通过 NPM 的官方源安装依赖好像很慢,动不动就要等上半天,如何解决?我们可以装一个 nrm!nrm 是 npm registry 管理工具,可以自由切换 npm registry,然后命令行使用时依然是 npm ,国内有很多 npm 的镜像,比如淘宝的 cnpm ,然而很多公司都架设了自己的私库。什么是私库?私库就是只能在公司内网访问,不能发布到 npm 共享平台的 npm 包,比如我们大公司私库的 registry 的名称就是 hnpm。不细说了,我们先装一个试试:

$ npm install -g nrm

然后根据官方教程我们先切一个国内的 registry,比如大淘宝的:

$ nrm use cnpm

然后用 NPM 随便安装个什么,看看速度如何?是不是很快^_^

等等,Node.js 还有。有的开发依赖包是有 Node.js 版本依赖的,我们知道 Node.js 不同大版本的功能还是差别很大的,但我们又不会一遍遍的卸载安装吧?感觉好蠢!好吧,我们当然可以装一个nvm,nvm?好像和 nrm 很像!nvm 是 Node.js 的版本管理工具,可以在多个终端切换和运行不同的 Node.js 版本,可以到这里参考具体的安装教程。不过 nvm 在 windows 下不能使用,没关系,这里还有几个替代工具:nvm-windowgnvm 供你选择。

同样,我们执行下命令验证安装成果:

$ nvm --version
0.33.0

项目初始化

有了上面的工具我们就可以开始创建一个项目了,我们执行以下命令来开始一个项目:

mkdir my-app
cd my-app
npm init

执行 npm init 后你会看到你需要输入项目的一些信息,完成后回车确认,然后npm会在根目录下创建一个叫 package.json 的文件,你之后通过 --save 或者 --save-dev 安装的依赖包都会出现在这个文件里。

先不管那么多,我们在根目录下创建一个 src 目录,然后在 src 下创建index.jsindex.html……,好吧,你可以按照下面的结构新建文件:

.
├── package.json
└── src
    ├── index.css
    ├── index.html
    └── index.js

在以下文件中输入代码:

index.js:

var el = document.createElement('div'),
  text = document.createTextNode('My App');

el.appendChild(text);
document.body.appendChild(el);

index.html:

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>My App</title>
</head>
<body>
</body>
</html>

我们要想办法让这个页面跑起来,what??? 就这么简单?,把js引入 index.html 不就完事儿了嘛?当然没那么简单,我们可是要搞高大上的东西的呢!

哈~跑题了,我们继续。

首先我们要装一个叫 webpack 的东西,它是一个模块打包器,也就是我们俗称的构建工具,之前的那些 GruntGulp 也都是构建工具,但是这年头流行 webpack 了!开个玩笑,webpack 的可扩展性和可插件化,以及把任何文件都视为模块的概念得到了前端社区的一致推崇,而且在打包效率和按需分割文件上都是其他几个构建工具无法相比较的,当然 webpack 的配置太灵活,官方文档写的太太太难看懂,也导致了很多初学者无从下手。

接下来我们就来配下这个神奇的工具吧。

自动构建

我们先安装下 webpack:

npm install --save-dev webpack

然后在根目录下新建一个 webpack.config.js 文件,输入以下代码:

let path = require('path');

module.exports = {
  entry: {
    app: path.resolve(__dirname, 'src', 'index.js')
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
};

但要想在浏览器中访问还得有个本地服务器,好在 webpack 都帮我们想到了,我们可以装一个webpack-dev-server:

npm install --save-dev webpack-dev-server

我们在 package.json 中增加个 npm scripts:

"scripts": {
  "start": "webpack-dev-server --port 3003"
},

ok!我们执行下 npm start,在浏览器中访问:http://localhost:3003。哎?好像哪里不对!是的,你得告诉 webpack,你的 bundle(打包后的 js)要插入到哪个 html 模板,前面说过,webpack 是插件化的,它把很多功能开放给了第三方来实现,他只是来负责拼装的,好,现在我们需要安装一个 html-webpack-plugin 插件:

npm install --save-dev html-webpack-plugin

修改下 webpack-config.js:

let HtmlWebpackPlugin = require('html-webpack-plugin'),
  path = require('path');

module.exports = {
  entry: {
    ...
  },
  ...
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'src', 'index.html')
    })
  ]
}

再次执行 npm start,页面可以正常访问了。

但是,这样似乎有点 low,我们新增一个文件 utils.js,搞点es6语法:

.
├── package.json
└── src
    ├── index.css
    ├── index.html
    ├── index.js
+   └── utils
+       └── utils.js

utils.js:

export function wordsToSentence(...words) {
  return words.join(' ');
}

修改 index.js

+ import { wordsToSentence } from './utils/utils';

let el = document.createElement('div'),
-  text = document.createTextNode('My App');
+  text = document.createTextNode(
+    wordsToSentence('Welcome', 'to', 'my', 'app!')
+  );

el.appendChild(text);
document.body.appendChild(el);

刷新页面后好像也没什么异常(你肯定用了 chrome 吧!),仔细看控制台的 source 的 app.js(你的 bundle)的代码片段:

"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence;
function wordsToSentence(...words) {
  return words.join(' ');
}

值得注意的是,使用 ES6 时需要考虑那些没有支持 ES6 的旧浏览器,虽然在 chrome 或者其他高级浏览器中没有出现问题,但不能保证在其他浏览器中能正常运行。为了万无一失,我们需要将 ES6 转换为 ES5,也就是js代码转换器,这类工具当今世界就属 Babel 最牛逼了:

npm install --save-dev babel-loader babel-core

稍等,装了 Babel 还没法用,还得搞个 presets:

npm install --save-dev babel-preset-env

在根目录下新建个 .babelrc,输入配置:

{
  "presets": ["env"]
}

修改 webpack.config.js,增加 babel 的支持:

...
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: path.resolve(__dirname, 'src')
      }
    ]
  },
  ...
};

执行 npm start,找到控制台 source 下的 app.js 代码片段:

"use strict";


Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.wordsToSentence = wordsToSentence;
function wordsToSentence() {
  for (var _len = arguments.length, words = Array(_len), _key = 0; _key < _len; _key++) {
    words[_key] = arguments[_key];
  }

  return words.join(' ');
}

已经成功转换成 ES5 代码。但是,目前 ES6 Modules 是由 Babel 来转的,你可以对比前后 2 次的代码片段的模块输出部分。现在,webpack 2 已经内 4 置了 ES6 Modules 的转换,据说效率和性能比 Babel 高!^_^没验证过哦,我们先试试,把 Babel 的模块转换关了先:

.babelrc

{
  "presets": [
    ["env", {
      "modules": false
    }]
  ]
}

执行 npm start 再次查看输出后的 app.js 的代码片段:

-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.wordsToSentence = wordsToSentence;
+/* harmony export (immutable) */ __webpack_exports__["a"] = wordsToSentence;
function wordsToSentence() {
  ...
}

模块输出方式又回到了使用 Babel 前的代码。

js 的环境似乎已经准备就绪,但 css 还没上场,我们来修改下 index.css:

#app {
  color: #57af09;
}

同时将 css 导入 bundle 入口,并修改下 index.js:

import './index.css';
import { wordsToSentence } from './utils/utils';

let el = document.createElement('div'),
...
el.id = 'app';
...

有了样式还不行,webpack 还需要相应的 loader 来处理 css 的模块:

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

修改下 webpack.config.js:

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.css$/,
        loader: ['style-loader', 'css-loader'],
        include: path.resolve(__dirname, 'src')
      }
    ]
  },
  ...
};

执行 npm start,现在可以看到页面已经有了样式。但是,我们说过,我们希望使用先进的武器:SASS。我们修改下 index.css:

$app-color: #57af09;

#app {
  color: $app-color;
}

再修改下文件后缀:

.
├── package.json
└── src
-   ├── index.css
+   ├── index.scss
    ...

修改 index.js 的入口:

-import './index.css';
+import './index.scss';

由于文件(模块)类型变了,我们还需要一个 SASS 的 webpack loader:

npm install --save-dev sass-loader node-sass

再次修改 webpack.config.js:

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
-       test: /\.css$/,
+       test: /\.scss$/,
-       loader: ['style-loader', 'css-loader'],
+       loader: ['style-loader', 'css-loader', 'sass-loader'],
        include: path.resolve(__dirname, 'src')
      }
    ]
  },
  ...
};

执行 npm start,webpack 编译没有报错,页面显示一切正常!

代码自动更新(热更新)

如果你尝试修改 index.scss 的样式,你有没注意到一个问题:页面会自动刷新。但有时候我们在开发一个模块,比如 dialog,刷新会导致你需要反复的在页面上操作才能看到这个 dialog 的样式更新。那我们有没有办法不刷新页面又能看到代码的更新呢?

其实很简单,因为 webpack-dev-server 已经内置了这样的功能,我们只要配置下 package.json的 npm scripts:

"scripts": {
  "start": "webpack-dev-server --hot --inline --port 3003"
},

注意到上面的代码,我们增加了 --hot --inline,让开发环境有了热更新的能力。我们重新执行 npm start,然后将你的浏览器和编辑器并排放置,然后反复修改 index.scss,你会看到页面不会刷新,但样式在自动的推送更新,这就是传说中的热更新

结束语

到这里,简单(简陋)的、现代化的前端开发环境已经有了基本的雏形,但是,本篇文章不是webpack 的使用指南,也不是 ES6 的语法教程,尽管如此,还是希望你通过本篇文章感受到前端开发在工程化领域的发展带来的惊喜。

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。


iKcamp
3.8k 声望666 粉丝

iKcamp由热爱原创和热翻译的小伙伴发起,成员来自美团点评、沪江等。成立于2016年7月,"iK"代表布兰登·艾克(JavaScript之父)。 追随JavaScript这门语言所秉持的精神,崇尚开放和自由的我们一同工作、分享、创作...