·  阅读 超级英雄webpack :头领战士

本文是[前端世界的超级英雄 —— 构建工具]系列的第一个专题,webpack计划会有三篇《超级英雄webpack :头领战士》,《超级英雄webpack :超能勇士》 《超级英雄webpack :领袖之证》。分别用webpack1,webpack3, webpack4。由浅入深的讲解webpack。本文会侧重webpack在项目中解决的问题与实战的应用。

1、知识点预览:

除了要讲故事,我们还是正经webpack教程,先梳理一下本文基本知识点:

  • 什么是webpack?
  • webpack的基本配置?
  • 什么是Loader?什么是Plugin?
  • webpack如何处理js,css,图片等静态资源文件?
  • webpack-dev-server和webpack --watch区别?
  • 如何配置多个模块的 Chunk?
  • 几个文件打包到同一个文件?几个文件打包到不同的文件?
  • 2、webpack概述

    好了,宝宝们,坐稳扶好。欢迎回到2012年的前端世界。

    随着JavaScript技术的发展,基于CommonJS的后端模块化规范和基于AMD前端模块化规范相继诞生。node童鞋能够用npm包管理工具轻松的管理自己所依赖的第三方库。由于npm中的第三方库只有部分支持AMD规范,大部分第三方库,前端童鞋不得不手动下载,放到指定目录下,再去引用。如果遇到,不支持AMD规范的库, 还需要配置shim,非常凄惨。

    著名的三大框架angular也在前一年诞生,web页面变成了一个能实现复杂交互,精美展示的完整应用程序。但随着而来的问题是,SPA(单页面应用)项目的体积越来越大,也没有办法动态加载CSS,通常我们会把CSS合并压缩成一个文件,但在糟糕的网络情况下,这简直是灾难。

    这一年,在德国纽伦堡一个叫 科伯斯 (Tobias Koppers) 的工程师,正潜心研究Google Web Toolkit工具中代码拆分(code splitting)的功能。因为在这个谷歌为Java程序员开发的工具中,它可以实现延迟加载不需要的代码,这对于提高大型应用加载速度非常重要。但在当时JavaScript还没有能实现这一功能的开源工具。

    不久后,科伯斯写出了这个可以“require anything”的前端打包工具—webpack。

    好了,现在我们就开始用webpack去改造 snap.svg 的demo。(借鉴了 Webpack from First Principles

    3、webpack实战

    虽然这只是个单页面,算不上单页面应用。但是存在的问题却是是一样的。

    首先分析一下 代码

  • 我们先在node中执行(node server.js),server.js怎么写的请看 《node.js的小美好》 。也可以将index.html文件直接在浏览器打开。
  • (1) 页面入口是index.html,加载了main.css,main.js
  • (2) main.css中包含了所有页面需要用的样式文件
  • (3) main.js中Snap是script标签通过加载snap.svg-min.js,生成的全局对象
  • 注意:要将chrome中Network下Disable cache打开,以免以后修改文件有缓存

    3.1、 安装webpack

    webpack是基于CommonJS的打包工具。我们首先给我们的项目安装webpack.(我们用的1.15.0版本是webpack1最后一个版本,2017年发布的。)

    npm init    //npm初始化
    npm install webpack -g  //全局安装webpack
    npm install webpack@1.15.0 --save-dev  //项目中安装webpack  
    touch webpack.config.js  // 新建一个webpack配置文件  
    复制代码

    我们新建一个webpack配置文件:webpack.config.js,然后配置入口文件和出口文件

    module.exports = {
      entry : './src/scripts/main.js',  //配置入口文件
      output : {
        path : './build',         //配置出口文件所在文件路径
        filename : 'bundle.js'    //配置出口文件名
    复制代码

    terminal中执行webpack

    webpack
    复制代码

    然后在index.html文件中,我们讲main.js文件路径修改为src="build/bundle.js"。这样我们就实现了webpack的编译打包。

    刷新浏览器能看到加载的bundle.js鳄鱼先生。我们将网速调整成3G,可以看到加载的文件数和加载速度。

    问题:webpack可以讲main.js文件打包成另一个,但并有没有体现出什么优势啊?

    3.2、 实现CommonJS模块化

    我们知道webpack是基于CommonJS实现了js文件的模块化,那么我们就去可以用CommonJS的方式引入脚本文件,不用自己下载再放到指定目录里面了。 我们项目中用到了snapsvg-cjs这个库,我们就先删除原来手动引入的文件,然后再用npm去加载

    rm -rf vendor //删除vendor文件夹
    npm install --save snapsvg-cjs  //npm加载snapsvg-cjs库
    复制代码

    然后我们在main.js中引入,var Snap = require('snapsvg-cjs'); 在index.html文中删除script标签引入的snapsvg-cjs.js文件

    webpack //执行webpack打包
    复制代码

    我们可以看到这一次加载的文件数就变成了13个。

    问题:现在我们前端童鞋也可以用npm包管理工具轻松管理自己依赖的第三方库了,但是既然是require anything,那么除了js脚本文件以外,还可以处理其他文件吗?

    3.3、 Loader

    webpack一个重要的机制就是可以通过不同的loader将css,图片等静态资源都打包进入js文件。

    3.3.1、 url-loader

    url-loader:会将引入的图片编码,生成dataURl,打包到文件中。

    我们想把在main.js里通过路径传进去的/src/assets/crocodile.svg文件,通过require方式引入,引入图片需要依赖 url-loader 这个加载器。

    npm install --save-dev url-loader  //安装url-loader
    复制代码

    在webpack.config.js文件中加入loader的配置

    module : {
      loaders : [
          test:/\.svg$/,
          loader:'url'
    
  • 在main.js中,将图片引入var crocodileUrl = require('../assets/crocodile.svg');
  • 以变量的形式,传到Snap.load()中。
  • 在终端里执行webpack打包
  • 这样我们图片已经以base64的格式打包到bundle.js文件中,请求的时候通过data协议直接加载。

    3.3.2、 style-loader与css-loader

  • css-loader: 加载.css文件
  • style-loader:配合css-loader使用,以形式在html页面中插入css代码
  • 删除index.html中的main.css,在main.js中引入require('../styles/main.css');

    npm install --save-dev css-loader style-loader //安装css-loader和style-loader
    复制代码

    在module配置css-loader和style-loader。

    module : {
        loaders : [
            test: /\.(svg|jpg)$/, //因为css样式中有背景图片,所以我们匹配jpg
            loader:'url'
            test: /\.css$/,
            loaders:[
              'style',
              'css'
    

    webpack编译后,你会看到,css文件都被打包到了bundle.js中进行动态加载。这里留个问题,由于css动态加载,加载时就会存在白屏问题,这个我们该怎么解决呢?

    学习到这你就理解了,“webpack可以通过不同的loader将css,图片等静态资源都打包进入js文件。”这句话的含义,这就是webpack“require anything”的具体实现。

    问题:css可以通过动态加载引入index.html,那么main.js文件,有没有什么办法自动引入index.html中呢?

    3.4、 Plugin

    Plugin: 插件目可以解决 loader 无法实现的其他事。

    3.4.1、 html-webpack-plugin

    //安装html-webpack-plugin
    npm install html-webpack-plugin --save-dev  
    //在webpack.config.js文件中引入
    var HtmlWebpackPlugin = require('html-webpack-plugin');
    plugins: [new HtmlWebpackPlugin()]
    复制代码

    执行webpack,你会发现在build文件夹中,多了一个index.html文件。我们已经成功将main.js打包进去了,

    那么如何与之前的文件上加一个main.js呢。我们只需要配置它的模版参数就可以啦。

    plugins: [new HtmlWebpackPlugin({
      template: 'index.html'
    复制代码

    我们访问http://localhost:8088/build/index.html,就可以看到我们的鳄鱼先生了。

    问题:还记得之前动态加载css白屏的问题吗?现在我们是似乎有办法解决了,那就是在把它从main.js文件中提出来就好了。这次我们该用什么plugin呢?

    3.4.2、 extract-text-webpack-plugin

    //安装html-webpack-plugin
    npm install extract-text-webpack-plugin@1.0.1  --save-dev  
    //在webpack.config.js文件中引入
    var ExtractTextPlugin = require('extract-text-webpack-plugin');
    plugins: [new HtmlWebpackPlugin()]
    然后我们配置loader
      test: /\.css$/,
      loader:ExtractTextPlugin.extract('style-loader','css-loader') //注意这里不是loaders
    复制代码

    这样我们就可以看到css被单独打包了呢。样式就先加载出来了。现在你可能会很好奇,为什么要在loader中还要去配置,webpack的工作流程是怎么样的?我们先在这里留个问题。 那你肯定又会问,这些loader,plugin,我怎么知道,它们都是做什么的呢,哈哈哈,google,百度啊。是不是觉得有点low,好吧,传送门awesome-webpack

    问题:开发时每次都需要执行一个webpack去编译是不是很烦,那么webpack有没有什么解决方法呢,当然有,可以执行webpack --watch这样webpack就可以自动编译。这就幸福了?天真!我还想要浏览器也自动刷新,这时候webpack --watch就无能为力了。我们需要学习webpack-dev-sever

    3.5 webpack-dev-server

    sudo npm install webpack-dev-server  //全局安装webpack-dev-ser
    npm install webpack-dev-server@1.16.5 --save-dev  //项目中安装webpack-dev-ser
    webpack-dev-server  //执行webpack-dev-ser
    复制代码

    访问http://localhost:8080/webpack-dev-server,现在我们就可以完成实时预览了。

    注意:webpack-dev-server只做文件实时预览服务。不做打包服务。所以你不会看到编译后的文件。但是webpack-watch是可以看到。

    但还有两个问题,一个是顶部有App ready看着比较别扭。这样的webpack-dev-server的配置问题,留到下一篇在webpack3中让《webpack :超能勇士》去解决吧。

    问题:另一个是文件中引用的图片路径显然不对,我该如何去配置?

    4、webpack常见配置

    恭喜你,你已经进来了webpack配置工程师角色。在于webpack打交道的时候最多的就是,你要了解各种配置,去解决你项目中的问题。那么我们现在就来学习一些常见的配置。

    1、处理html文件中的图片路径,配置html-loader

    test: /\.html$/, loader: "html-loader" 复制代码

    2、配置多个入口文件Chunk

      entry : {
        'main' : ['./src/scripts/main.js'],
        'page' : ['./src/scripts/page.js']
      output : {
        path : './build',
        filename : './js/[name].js'
    复制代码

    webpack编译后,我们能看到打包好的main.js和page.js文件,它们也都被加载到index.html文件中

    3、配置多页面文件,指定加载模块

    我们想main.js和page.js分别被加载到index.html文件和page.html文件中,而不是都加载到index.html中

      new HtmlWebpackPlugin({
        template: './src/index.html',
        filename:  'index.html',
        chunks: ['main'] 
      new HtmlWebpackPlugin({
        template: './src/page.html',
        filename:  'page.html',
        chunks: ['page'] 
    复制代码

    HtmlWebpackPlugin还有一个常用的参数是一个文件加hash。我们下一篇去配置

    4、提取公共模块,webpack.optimize.CommonsChunkPlugin插件

    比如base.js是一个公共模块,它需要被加载到每个页面中去。

  • 我们需要新建一个关于base.js的chunk,'common' : ['./src/scripts/base.js']
  • 在HtmlWebpackPlugin中添加进去, chunks: ['page','common']
  • 引入webpack.optimize.CommonsChunkPlugin
  •  var webpack = require('webpack');
     new webpack.optimize.CommonsChunkPlugin({
          name: 'common',
          filename: './js/base.js' 
    复制代码

    webpack.optimize.CommonsChunkPlugin插件的作用是能自动分析每个页面引入的共同模块,并且都编译进base.js文件中。比如,在page.js和main.js文件中,我们都引用了main.css。

    这样base.js就又两部分组成

  • base引用的chunk,即common
  • 自动分析的公共模块,如果只想引用公共模块,可以把name定义为默认的commons
  • 好了,这一篇就到这里了,是不是还有点不舍呢。宝宝们,跟鳄鱼先生说再见。下一篇《超级英雄webpack :超能勇士》

    分类:
    前端
  •