webpack启蒙教学

自我介绍环节

webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler) 。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle(通常只有一个,由浏览器加载)。

webpack.png

如上是webpack官网上的简介。从中我们可以看出webpack是一个模块打包器,它获取带依赖的模块并产生出与这些模块相对于的静态资源。然而,自从发布后,webpack逐渐发展成为所有前端代码的管理工具。

为什么使用Webpack

分块转换的想法
模块要能够在客户端中去执行,则必须将它们通过请求从server端下载到browser端。通常一个请求对应一个模块,只有需要的模块会被转换。当所有资源都在一个模块中时,不需要的模块也会被转换,这样就显得很浪费资源和时间。将众多的模块切成许多片,在初始化时的请求不会包括完整的代码,并且在初始化时不需要的模块切片会在后续加载过程中按需加载。并且将模块化的切片方式是可以由开发人员自己定义的。

常用的模块系统解决方案:
一、<script>标签类型
最原汁原味的脚本引入方案,缺点如下(请不要问我为什么不介绍优点):

  • 全局作用域下造成变量的冲突;
  • 文件加载顺序很重要;
  • 模块与模块之间的依赖要解决;
  • 在大型项目中难以维护和管理;

二、CommonJs
该规范的核心思想是允许模块通过require方法来同步加载所要依赖的其他模块,然后通过exportsmodule.exports来导出需要暴露的接口。

require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;

优点:

  • 服务端模块能够重复利用;
  • 有优秀的包管理工具npm;
  • 简单,上手容易;

缺点:

  • 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的;
  • 不能非阻塞的并行加载多个模块;

三、AMD
由于浏览器端的模块不能采用同步的方式加载,会影响后续模块的加载执行,因此AMD(Asynchronous Module Definition异步模块定义)规范诞生了。

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });

require接口用来加载一系列模块,define接口用来定义并暴露一个模块。
优点:

  • 适合浏览器的异步加载机制;
  • 并行加载模块;

缺点:

  • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;

四、CMD
CMD(Common Module Definition)规范和AMD很相似,尽量保持简单,并与CommonJS和Node.js的 Modules 规范保持了很大的兼容性。在CMD规范中,一个模块就是一个文件。

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

优点:

  • 依赖就近,延迟执行;
  • 可以很容易在 Node.js 中运行;

缺点:

  • 依赖 SPM 打包,模块的加载逻辑偏重;

五、ES6
EcmaScript6标准增加了JavaScript语言层面的模块体系定义。在 ES6 中,我们使用export关键字来导出模块,使用import关键字引用模块。需要说明的是,ES6的这套标准和目前的标准没有直接关系,目前也很少有JS引擎能直接支持。

import "jquery";
export function doStuff() {}
module "localModule" {}

优点:

  • 未来的ES规范;

缺点:

  • 浏览器对ES6的支持还需要一段时间;
  • 能够依赖的现有的模块少;

webpack的目标如下:

  • 拆分依赖树成块并按需加载;
  • 让初始化加载时间更少;
  • 每一个静态资源应该是一个模块;
  • 能够集成第三方类库;
  • 适用于大型项目;
  • 能够定制模块打包的每一个部分;

webpack较之其他类似工具有什么不同?

  • 有同步和异步两种不同的加载方式;
  • 加载器(Loaders)可以将其他资源整合到JS文件中;
  • 支持 CommonJs AMD 规范有丰富的开源插件库,可以根据自己的需求自定义webpack的配置

众所周知作为一个SEO的小TIPS,浏览器加载的资源越少,响应的速度也就越快,所以有时候我们通常会尽可能的将资源合并到一个主文件app.js里面。但有时候也会适得其反:当项目十分庞大的时候,不同的页面不能做到按需加载,而是将所有的资源一并加载,耗费时间长,性能降低。会导致依赖库之间关系的混乱,特别是大型项目时,会变得难以维护和跟踪。

虽然webpack也会将所有资源放在一个文件里面,但是webpack可以很好的解决以上缺点,因为它是一个十分聪明的模块打包系统,当你正确配置后,它会比你想象中的更强大,更优秀。

webpack工作流程.png

核心概念

入口(Entry)
webpack 创建应用程序所有依赖关系图的起点。入口告诉 webpack 从哪里开始,并根据依赖关系图确定需要打包的内容。可以将应用程序的入口认为是根上下文(contextual root)app 第一个启动文件。参见如下示例:

// 单入口
module.exports = {
  entry: {
     main: './src/main.js'
  }
}

// 多入口
module.exports = {
  entry: {
     app: ["./home.js", "./events.js"]
  }
}

// 多入口,app(应用主入口),vendors(公共库)
module.exports = {
  entry: {
     app: './src/main.js',
     vendors: './src/vendors.js'
  }
}

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

const path = require('path');
module.exports = {
  output: {
    // 打包输出的目录,这里是绝对路径,必选设置项
    path: path.resolve(__dirname, './dist'),
    // 资源基础路径
    publicPath: '/dist/',
    // 打包输出的文件名,也可以设置为[name].js,动态对应入口的定义
    filename: 'my-first-bundle.js' 
  }
}

加载器(Loader)
webpack 会把所有引用到的资源(.css, .html, .scss, .jpg, etc.) 都作为模块处理。然而 webpack 自身只理解 JavaScript。webpack loader 在文件被添加到依赖图中时,将其转换为模块。在 webpack 的配置中 loader 有两个目标:

  • 识别出(identify)应该被对应的 loader 进行转换(transform)的那些文件;
  • 转换这些文件(test 属性),从而使其最终添加到 bundle 中(use 属性);

示例如下:

module.exports = {
  module: {
    rules: [
      {
        // 这是个正则表达式
        test: /\.jsx$/,
        // 指定loader
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        // 指定多个loader
        use: [
          'css-loader',
          'style-loader'
        ]
      }
    ]
  }
}

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

const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); 

// 应用插件
const path = require('path');
const config = { 
    main: './src/main.js',
    output: { 
        path: path.resolve(__dirname, 'dist'), 
        filename: 'my-first-bundle.js' 
    }, 
    module: { 
        rules: [ 
            { test: /\.txt$/, use: 'raw-loader' } 
        ] 
    }, 
    plugins: [ 
        // 压缩JS
        new webpack.optimize.UglifyJsPlugin(), 
        // 生成HTML
        new HtmlWebpackPlugin({template: './src/index.html'}) ,
        // 分离入口文件vendor作为单独的模块
        new webpack.optimize.CommonsChunkPlugin({
           name: 'vendor',
           filename: 'vendor.js'
        })
    ]
};
module.exports = config;

完整配置文件DEMO

**webpack.config.js**

var path = require('path'),
webpack = require('webpack'),
ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  devtool: 'eval',
  entry: {
    main: './src/main.js'
  },
  resolve: {
    // 自动解析确定的扩展
    extensions: ['.js', '.jsx'],
    // 告诉 webpack 解析模块时应该搜索的目录
    modules: [
      path.resolve(__dirname, 'src'),
      'node_modules'
    ],
    alias: {
      'src': path.resolve(__dirname, './src')
    }
  },
  output: {
    // 打包输出的目录,这里是绝对路径,必选设置项
    path: path.resolve(__dirname, './dist'),
    // 资源基础路径
    publicPath: '/dist/',
    // 打包输出的文件名
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        }
      },
      {
        test: /\.css$/,
        /*
        use: [
          'css-loader',
          'style-loader'
        ]
        */
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader?minimize'
        })
      },
      // 支持less
      {
        test: /\.less$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            { loader: "css-loader?minimize" },
            { loader: "less-loader" }
          ]
        })
      },
      {
        // 处理图片文件
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 7186, // inline base64 if <= 7K
          name: 'static/images/[name].[ext]'
        }
      },
      {
        // 处理字体文件
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 7186, // inline base64 if <= 7K
          name: 'static/fonts/[name].[ext]'
        }
      }
    ]
  },
  plugins: [
    // https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new ExtractTextPlugin({ filename: 'static/css/app.css', allChunks: true })
  ]
}

推荐阅读更多精彩内容

  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 webpack介绍和使用 一、webpack介绍 1、由来 ...
    it筱竹阅读 6,521评论 0 17
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 10,333评论 7 109
  • 无意中看到zhangwnag大佬分享的webpack教程感觉受益匪浅,特此分享以备自己日后查看,也希望更多的人看到...
    小小字符阅读 6,240评论 6 33
  • 最近在学习 Webpack,网上大多数入门教程都是基于 Webpack 1.x 版本的,我学习 Webpack 的...
    My_Oh_My阅读 6,899评论 40 246
  • 书,不知要从何时说起。对于书的情感个人的情感很淡,上学的那些年并不是能沉得住气的人,都说看书的人能耐得住寂寞,在现...
    Zachary鴹羽阅读 27评论 0 0