webpack入门

写在前面

第一次接触webpack,是在一个react项目参与中,刚开始使用的时候,甚至不知道是做什么用的,只看到webpack.config.js文件中很多配置,但是都不太清楚是何用处,本地开发调试模式和build到生产环境都分不清,甚至曾经在服务器上用开发模式运行过一段时间,菜的抠脚啊!最早只知道jQuery操控dom,js、css引入到html,浏览器就渲染出页面了。完全不了解webpack这种构建工具,还可以通过express搭建个native-server,来实时调试代码修改,当然了这个功能一般都是webpack自动完成的。我们可以在本机端口,访问到我们的页面,代码的修改保存可以实时刷新重新渲染。

一、概念

webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler)。所谓的模块就是在平时的前端开发中,用到一些静态资源,如JavaScript、CSS、图片等文件,webpack就将这些静态资源文件称之为模块。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle - 通常只有一个,由浏览器加载。
它是高度可配置的,但是,在开始前你需要先理解四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)。

1.入口(Entry)

webpack 创建应用程序所有依赖的关系图(dependency graph)。图的起点被称之为入口起点(entry point)入口起点告诉 webpack 从哪里开始,并根据依赖关系图确定需要打包的内容。可以将应用程序的入口起点认为是根上下文(contextual root)app 第一个启动文件
在 webpack 中,我们使用 webpack 配置对象(webpack configuration object) 中的entry
属性来定义入口
接下来我们看一个最简单的例子:webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js'};

根据不同应用程序的需要,声明entry属性有多种方式。

2.出口(Output)

将所有的资源(assets)归拢在一起后,还需要告诉 webpack 在哪里打包应用程序。webpack 的output属性描述了如何处理归拢在一起的代码(bundled code)。
webpack.config.js

const path = require('path');  
module.exports = {
  entry: './path/to/my/entry/file.js',  
  output: { 
      path: path.resolve(__dirname, 'dist'),
      filename: 'my-first-webpack.bundle.js'
 }}

在上面的例子中,我们通过output.filename和output.path
属性,来告诉 webpack bundle 的名称,以及我们想要生成(emit)到哪里。
你可能看到项目生成(emitted 或 emit)贯穿我们整个文档和插件 API。它是“生产(produced)”或“排放(discharged)”的特殊术语。

3.Loader

webpack 的目标是,让 webpack 聚焦于项目中的所有资源(asset),而浏览器不需要关注考虑这些(明确的说,这并不意味着所有资源(asset)都必须打包在一起)。webpack 把每个文件(.css, .html, .scss, .jpg, etc.) 都作为模块处理。然而 webpack 自身只理解 JavaScript
webpack loader 在文件被添加到依赖图中时,其转换为模块。**
在更高层面,在 webpack 的配置中 loader 有两个目标。
识别出(identify)应该被对应的 loader 进行转换(transform)的那些文件。(test
属性)
转换这些文件,从而使其能够被添加到依赖图中(并且最终添加到 bundle 中)(use属性)

webpack.config.js

const path = require('path');
const config = { 
   entry: './path/to/my/entry/file.js', 
   output: { 
       path: path.resolve(__dirname, 'dist'), 
       filename: 'my-first-webpack.bundle.js' 
},
   module: {
       rules: [ { 
          test: /\.txt$/, 
          use: 'raw-loader'
 } ] }
};
module.exports = config;

以上配置中,对一个单独的 module 对象定义了rules属性,里面包含两个必须属性:test和use。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在require()/import语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先使用 raw-loader转换一下。”

重要的是要记得,在 webpack 配置中定义 loader 时,要定义在module.rules中,而不是rules。然而,在定义错误时 webpack 会给出严重的警告。为了使你受益于此,如果没有按照正确方式去做,webpack 会“给出严重的警告”

4.插件(Plugins)

然而由于 loader 仅在每个文件的基础上执行转换,而插件(plugins)
更常用于(但不限于)在打包模块的 “compilation” 和 “chunk” 生命周期执行操作和自定义功能(查看更多)。webpack 的插件系统极其强大和可定制化。
想要使用一个插件,你只需要require()它,然后把它添加到plugins
数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用new
来创建它的一个实例。
webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');//installed via npmconst 
webpack = require('webpack'); //to access built-in plugins
const path = require('path');
const config = {
   entry: './path/to/my/entry/file.js',
   output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'my-first-webpack.bundle.js' 
}, 
   module: {
      rules: [ {
          test: /\.txt$/,
          use: 'raw-loader' 
} ] },
    plugins: [ 
     new webpack.optimize.UglifyJsPlugin(), 
     new HtmlWebpackPlugin({template: './src/index.html'}) 
]};
module.exports = config;

二、Webpack的核心原理

Webpack的两个最核心的原理分别是:

  1. 一切皆模块正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require('myJSfile.js')亦可以require('myCSSfile.css')。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。
  2. 按需加载传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。

三、几处说明

1.开发模式和生产模式

在package.json文件加入如下的scripts项:

"scripts": {
// 运行npm run build 来编译生成生产模式下的bundles
"build": "webpack --config webpack.config.prod.js",
// 运行npm run dev来生成开发模式下的bundles以及启动本地server
"dev": "webpack-dev-server"
}

2.webpack-dev-server

我们每修改一次就要需要输入 npm run dev 是一件非常无聊的事情,幸运的是,我们可以把让他自己运行,那就是使用webpack-dev-server。

除了提供模块打包功能,Webpack还提供了一个基于Node.js Express框架的开发服务器,它是一个静态资源Web服务器,对于简单静态页面或者仅依赖于独立服务的前端页面,都可以直接使用这个开发服务器进行开发。在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。

  • 安装:
    npm install --save-dev webpack-dev-server
  • 调整npm的package.json中scripts 部分开发命令的配置
{
  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "pub": "webpack --config webpack.pub.config.js",
      "dev": "webpack-dev-server  --config webpack.dev.config.js --devtool eval --progress --colors --hot --content-base src"
  }
}

在dev的配置中做了以上改变之后,webpack-dev-server 会在 localhost:8080 建立一个 Web 服务器。
几个参数的解释:
--devtool eval:为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
--progress:显示合并代码进度
--colors -- hot:命令行中显示颜色
--content-base 指向设置的输出目录
--手动访问 http://localhost:8080
简单来说,当你运行 npm run dev的时候,webpack会帮你会启动一个 Web 服务器,然后监听文件修改,然后自动重新合并你的代码。真的非常简洁。

注意点
用webpack-dev-server生成bundle.js文件是在内存中的,并没有实际生成;
如果引用的文件夹中已经有bundle.js就不会自动刷新了,你需要先把bundle.js文件手动删除(后期有插件可以完成);
用webstorm的同学注意了,因为webstorm是自动保存的,所以可能识别的比较慢,你需要手动的ctrl+s一下;

几个报错

  • webpack版本的问题
    如果webpack使用的1.x的版本,那么webpack-dev-server也要使用1.x的版本,否则会报如下错误:Connot find module 'webpack/bin/config-yargs'。
  • 端口占用问题
    如果已经有一个工程中使用了webpack-dev-server,并且在运行中,没有关掉的话,那么8080端口就被占用了,此时如果在另一个工程中使用webpack-dev-server就会报错:Error: listen EADDRINUSE 127.0.0.1:8080。
    webpack-dev-server(有利于在开发模式下编译)
    这是一个基于Express.js框架开发的web server,默认监听8080端口。server内部调用Webpack,这样做的好处是提供了额外的功能如热更新“Live Reload”以及热替换“Hot Module Replacement”(即HMR)。
    webpack-dev-server的“hot” 和 “inline”选项
    “inline”选项会为入口页面添加“热加载”功能,“hot”选项则开启“热替换(Hot Module Reloading)”,即尝试重新加载组件改变的部分(而不是重新加载整个页面)。如果两个参数都传入,当资源改变时,webpack-dev-server将会先尝试HRM(即热替换),如果失败则重新加载整个入口页面。

// 当资源发生改变,以下三种方式都会生成新的bundle,但是又有区别:
// 1. 不会刷新浏览器
$ webpack-dev-server
//2. 刷新浏览器
$ webpack-dev-server --inline
//3. 重新加载改变的部分,HRM失败则刷新页面
$ webpack-dev-server --inline --hot

3.“entry”:值分别是字符串、数组和对象的情况

  • 数组类型
    添加多个彼此不互相依赖的文件,你可以使用数组格式的值。
  • 对象
    现在,假设你的应用是多页面的(multi-page application)而不是SPA,有多个html文件(index.html和profile.html)。然后你通过一个对象告诉Webpack为每一个html生成一个bundle文件。
    以下的配置将会生成两个js文件:indexEntry.js和profileEntry.js分别会在index.html和profile.html中被引用。
  • 混合类型
    enter对象里使用数组类型,例如下面的配置将会生成3个文件:vender.js(包含三个文件),index.js和profile.js文件。
57ece0f30001dd1c08000371.png

4. output:“path”项和“publicPath”项output项

告诉webpack怎样存储输出结果以及存储到哪里。output的两个配置项“path”和“publicPath”可能会造成困惑。
“path”仅仅告诉Webpack结果存储在哪里,然而“publicPath”项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。

5..babelrc 文件

babal-loader使用”presets“配置项来标识如何将ES6语法转成ES5以及如何转换React的JSX成js文件。我们可以用如下的方式使用”query“参数传入配置:

module: {
 loaders: [ { test: /\.jsx?$/,
 exclude: /(node_modulesbower_components)/, 
 loader: 'babel',
 query: { presets: ['react', 'es2015'] } } ]
 }

然而在很多项目里babal的配置可能比较大,因此你可以把babal-loader的配置项单独保存在一个名为”.babelrc“的文件中,在执行时babal-loader将会自动加载.babelrc文件。
所以在很多例子里,你可能会看到:

//webpack.config.js module:
 {
 loaders: [ { test: /\.jsx?$/, 
exclude: /(node_modulesbower_components)/, 
loader: 'babel' } ] 
} 
//.bablerc 
{ presets: ['react', 'es2015'] }

6.plugin插件

插件一般都是用于输出bundle的node模块。
例如,uglifyJSPlugin获取bundle.js然后压缩和混淆内容以减小文件体积。
类似的extract-text-webpack-plugin内部使用css-loader和style-loader来收集所有的css到一个地方最终将结果提取结果到一个独立的”styles.css“文件,并且在html里边引用style.css文件。

//webpack.config.js 
// 获取所有的.css文件,合并它们的内容然后提取css内容到一个独立的”styles.css“里
 var ETP = require("extract-text-webpack-plugin");
 module: {
 loaders: [ {
    test: /\.css$/, 
    loader:ETP.extract("style-loader","css-loader") } 
] }, 
    plugins: [ new ExtractTextPlugin("styles.css") //Extract to styles.css file ]
 }

注意:如果你只是想把css使用style标签内联到html里,你不必使用extract-text-webpack-plugin,仅仅使用css loader和style loader即可:

module: {
 loaders: [{
     test: /\.css$/, 
     loader: 'style!css' // (short for style-loader!css-loader)
 }]

7.加载器和插件

加载器就是webpack准备的一些预处理工具,比如编译jsx和es6的加载器,处理sass等....

使用加载器的步骤也很简单,首先是安装依赖,然后在配置文件的module中加一个字段module字段,在module写上loaders,在loaders中写上相应的配置。
常用加载器:

  • 编译jsx和ES6到原生js
    安装以下的依赖
    npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react
    修改开发配置文件
module: {
  loaders: [
      {
          test: /\.jsx?$/, // 用正则来匹配文件路径,这段意思是匹配 js 或者 jsx
          loader: 'babel',// 加载模块 "babel" 是 "babel-loader" 的缩写
          query: {
              presets: ['es2015', 'react']
          }
      }
  ]
}
  • 加载CSS
    加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情,css-loader会遍历 CSS 文件,然后找到 url() 表达式然后处理他们,style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中。

Loader处理单独的文件级别并且通常作用于包生成之前或生成的过程中。
而插件则是处理包(bundle)或者chunk级别,且通常是bundle生成的最后阶段。一些插件如commonschunkplugin甚至更直接修改bundle的生成方式。

四、webpack的特点

  • 对 CommonJS 、AMD 、ES6的语法做了兼容;
  • 对js、css、图片等资源文件都支持打包;
  • 串联式 模块加载器 以及 插件机制 ,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持;
  • 有独立的配置文件webpack.config.js;
  • 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间;
  • 支持 SourceUrls 和 SourceMaps,易于调试;
  • 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活;
  • webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快;
    webpack最常用与spa应用,主要是vue和React,其实它就非常像Browserify,但是将应用打包为多个文件。如果单页面应用有多个页面,那么用户只从下载对应页面的代码. 当他么访问到另一个页面, 他们不需要重新下载通用的代码。

推荐阅读更多精彩内容

  • 无意中看到zhangwnag大佬分享的webpack教程感觉受益匪浅,特此分享以备自己日后查看,也希望更多的人看到...
    小小字符阅读 6,926评论 7 35
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 11,431评论 7 109
  • Webpack 第一章 Webpack 简介 Instagram团队在进行前端开发的过程中,发现当项目组成员越来越...
    whitsats阅读 376评论 0 1
  • 1.为什么要使用webpack 现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代...
    YINdevelop阅读 233评论 0 5
  • 作者:亚帝度假式家具 Arthur 巴黎机场的候机楼色彩鲜艳,有各种游戏,充满人性的关怀和活力。在这里弹奏几曲,聊...
    b9cc4465260e阅读 118评论 0 0