Webpack

371 阅读4分钟

本篇笔记使用的webpack版本为3.6.0 ,因为vue cli2依赖于这个版本

什么是webpack

官方解释:At its core, **webpack **is a static module bundler for modern JavaScript applications.

从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。

image.png

和grunt/gulp的对比

  • grunt/gulp的核心是Task

    • 我们可以配置一系列的task,并且定义task要处理的事务(例如ES6、ts转化、图片压缩、scss转专成css)
    • 之后让grunt/gup来依次执行这些task,而且让整个流程自动化。
    • 所以 grunt/gulp也被称为前端自动化任务管理工具。
  • 我们来看一个gulp的task

    • 下面的task就是将src下面的所有js文件转成ES5的语法。
    • 并且最终输出到dist文件夹中。
    const gulp = require('gulp');
    const babel = require('gulp-babel');
    
    gulp.task('js',()=>{
       gulp.src('src/*.js')
       .pipe(babel({
          presets:['es2015']
       }))
       .pipe(gulp.dest('dist'))
    });
    
  • 什么时候用 grunt/gulp呢?

    • 如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。
    • 只需要进行简单的合并、压缩,就使用 grunt/gulp即可。
    • 但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的 webpack了。
  • 所以,grunt/gulp和 webpack有什么不同呢?

    • grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
    • webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。

webpack起步

一般项目结构

  • dist文件夹 distribution 发布后将这个文件夹部署到服务器即可
  • src文件夹 用来存放代码文件

打包

//在src文件夹中的main.js
const xx = require('./other');
console.log(xx.add(1,2));
//在src文件夹中的other.js
function add(num1,num2){
	return num1+num2;
}
let a = 999;
module.exports = {add,a}
#进入项目文件夹后
webpack ./scr/main.js ./dist/bundle.js
#webpack 入口文件 目标文件

配置打包

在项目根目录下创建webpack.config.js文件,文件名必须是这个

//在此之前最好npm init一下
//webpack.config.js内容
const path = require('path');
module.exports={
	entry:'./src/main.js',
	output:{
		path:path.join(__dirname,'./dist'),
		filename:'bundle.js'
	}
}

形成映射

//npm init 初始化文件 package.json
{
  "name": "start",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"//npm run test执行这条命令,类同
    "build":"webpack"//以后使用npm run build 替代 webpack 而且会优先找本地的webpack而非全局
  },
  "author": "me",
  "license": "ISC"
}

文件处理

CSS文件处理

  1. 安装css-loaderstyle-loader

    npm install css-loader --save-dev
    #--save-dev为开发时依赖
    #--save-dev的模块,在npm install的时候不会被下载到node_modules文件夹中,而--save会
    
  2. 配置webpacka..config.js

    const path = require('path');
    module.exports={
       entry:'./src/main.js',
       output:{
          path:path.join(__dirname,'./dist'),
          filename:'bundle.js'
       },//下面这段
       module:{
          rules:[
             {
                test:/\.css$/,//切记注意这里不是字符串,是正则表达式
                // css-loader只负责将css文件进行加载
                // style-loader负责将样式添加到DOM中
                // 读取loader的顺序是从右向左
                use:['style-loader','css-loader']
             }
          ]
       }
    }
    
  3. 将CSS文件作为模块,按照require方法导入main.js

    const xx = require('./js/other');
    require('./css/common.css');
    console.log(xx.add(1,2));
    
  4. 执行打包代码

Less文件处理

  1. 安装less-loaderless

    #npm install less-loader less --save-dev
    
  2. 配置webpacka..config.js

    const path = require('path');
    module.exports={
       entry:'./src/main.js',
       output:{
          path:path.join(__dirname,'./dist'),
          filename:'bundle.js'
       },
       module:{
          rules:[
             {
                test:/\.css$/,
                // css-loader只负责将css文件进行加载
                // style-loader负责将样式添加到DOM中
                // 使用顺序是从右向左
                use:['style-loader','css-loader']
             },//添加下面这段
             {
                test: /\.less$/,
                use:['style-loader','css-loader','less-loader'] // 将 Less 编译为 CSS
             }
          ]
       }
    }
    
  3. 将less文件作为模块,按照require方法导入main.js

    const xx = require('./js/other');
    require('./css/common.css');
    require('./css/special.less');
    console.log(xx.add(1,2));
    
  4. 打包

图片文件处理

  1. 安装url-loaderfile-loader

    npm install url-loader file-loader --save-dev
    
  2. 配置webpacka..config.js

    {
       test: /\.(png|jpg|gif|jpeg)$/,
          use:[
             {
                loader:'url-loader',
                options:{
                   limit:8192//只处理<8kb的图片,会将图片转为base64类型的字符串
                   //在图片>8kb的时候,会去寻找file-loader来处理
                   //默认被file-loader打包好的名字为32位的哈希值,是为了防止重复等等
                   //但是在真正项目开发中,并不是适合,所以添加name选项
                   name:'img/[name].[hash:8].[ext]'
                   //这样把图片文件放到dist文件夹下的img文件夹下
                   //[name]为文件原名+[hash:8]8位哈希值防重复+[ext]后缀名
                }
             }
          ] 
    }
    
  3. file-loader打包的图片会到dist文件夹中,由于路径不对,即使打包成功也不会显示图片,需要再配置一下webpack.config.js

    const path = require('path');
    module.exports={
       entry:'./src/main.js',
       output:{
          path:path.join(__dirname,'./dist'),
          filename:'bundle.js',
          publicPath:'dist/'//添加公共路径
       },
       module:{
          //...
       }
    }
    
  4. 打包

Vue文件处理

  1. 安装vue-loadervue-template-compiler

    npm install vue-loader@13 vue-template-compiler --save-dev
    #15.*之后版本需要一个插件,不知道是什么
    
  2. 配置webpack-config.js

    {
    	test:/\.vue$/,
    	use:['vue-loader']
    }
    
  3. 打包

Babel

webpack打包完后,仔细观察浏览器中的JS代码,发现代码还是ES6版本,但是为了多浏览器支持,必须要转化为ES5,这里使用Babel来转化ES6ES5

  1. 安装babel

    npm install babel-loader@7 babel-core babel-preset-es2015 --save-dev
    #按教程安装7版本,为了防止错误
    
  2. 配置webpack.config.js

    module: {
       rules: [
          {
             test: /\.m?js$/,
             exclude: /(node_modules|bower_components)/,//表示排除(不匹配)这些文件夹
             use: {
                loader: 'babel-loader',
                options: {
                   presets: ['es2015']
                }
             }
          }
       ]
    }
    
  3. 打包

配置Vue

  1. 安装Vue

    npm install vue --save
    
  2. 配置webpack.config.js

    module.exports={	
       resolve:{
          alias:{
             'vue$':'vue/dist/vue.esm.js'
             //默认会找node_modules/vue/dist/vue.runtime.js,但这个运行时不会解析template模板
             //所以在这里配置,主动更改默认Vue版本
          }
       }
    }
    
  3. 使用Vue

    import Vue from 'vue'
    const app = new Vue({
       el:'#app',
       data:{
          message:'我是测试内容'
       }
    })
    
  4. 打包

Vue项目的终极方案

  • 某Vue方案
    • dist 打包生成的文件放入该该文件夹
    • node_modules npm下载的包
    • src 资源
      • css
      • js
      • img
      • vue
        • 各种.vue文件,相互注册,形成树结构
      • main.js 引入vue文件根结构和各种js文件
      • ...
    • index.html 主页
    • package.json npm init 生成
    • package-lock.json npm init 生成
    • webpack.config.js webpack配置

plugin插件的使用

简单使用横幅插件

配置webpack.config.js

const webpack = require('webpack');	
plugins:[
   new webpack.BannerPlugin("本项目版权归wc所有")
]

以后打包的bundle.js文件第一行为/*本项目版权归wc所有*/

打包html的plugin

  • 目前,我们的index.html文件是存放在项目的根目录下的。

    • 我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。

    • 所以,我们需要将index.html件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件

  • HtmlWebpackPlugin插件可以为我们做这些事情:

    • 自动生成一个index.html文件何可以指定模板来生成
    • 将打包的js文件,自动通过script标签插入到body中
  • 安装HtmlWebpackPlugin插件

    npm install html-webpack-plugin --save-dev
    
  • 使用插件,修改webpack.config.js文件中plugins部分的内容如下:

    • 这里的template表示根据什么模板来生成index.html
    • 另外,我们需要删除之前在 output中添加的publicPath属性
    • 否则插入的script标签中的src可能会有问题
const HtmlWebpackPlugin = require('html-webpack-plugin');
//部分代码
plugins:[
	new webpack.BannerPlugin("本项目版权归wc所有"),
	new HtmlWebpackPlugin({
		template:'index.html'
	})
]

因为打包完后bundle.jsindex.html在同一目录下,所以需要对webpack.config.js配置

// publicPath:'dist/' 将这个注释掉或者删除

JS压缩的Plugin

  • 在项目发布之前,我们必然需要对js等文件进行压缩处理

    • 这里,我们就对打包的js文件进行压缩
    • 我们使用一个第三方插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
    npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
    
  • 修改webpack.config.js文件,使用插件:

    const path = require('path')
    const webpack = require('webpack')
    const uglifyJsPlugin = require('uglifyjs-webpack-plugin')
    
    module.exports = {
       ...
       plugins:[
          new webpack.BannerPlugin('最终版权归XXX所有')
          new uglifyJsPlugin()
       ]
    }
    
  • 查看打包后的bundle.js文件,是已经被压缩过了的。

该插件和横幅插件冲突,因为该插件会删除所有注释

webpack-dev-server搭建本地服务器

  • webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用Express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。

  • 不过它是一个单独的模块,在webpack中使用之前需要先安装它

    npm install -save-dev webpack-dev-server@2.9.1
    
  • devServer也是作为webpack中的一个选项,选项本身可以设置如下属性:

    • contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我这里要填写./dist
    • port:端口号
    • inline:页面实时刷新
    • historyApiFallback:在SPA页面中,依赖HTML5的history模式
  • webpack.config.js文件配置修改如下:

    devServer:{
       contentBase: './dist',
       inline: true
    }
    
  • 我们可以再配置另外一个scripts:

    • --open参数表示直接打开浏览器
    'dev':'webpack-dev-server --open'
    

与图中2.9.1修改为2.9.3,为了和webpack版本对应

配置文件的抽离

​ 开发中会遇到这样的问题:当我们要进行调试测试时需要打包项目,但是配置文件中的uglify插件会让压缩JS代码,这样反而不好修改JS文件的代码了。而在真正上线项目时,也不需要搭建本地服务器,所以devServer选项也不需要。

​ 所以我们要把webpack.config.js文件分离成多个版本。

安装webpack-merge

npm install webpack-merge@4.1.5 --save-dev
#用与合并配置文件

分离

原始webpack.config.js如下

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const uglify = require('uglifyjs-webpack-plugin');
module.exports={
   entry:'./src/main.js',
   output:{
      path:path.join(__dirname,'./dist'),
      filename:'bundle.js',
      // publicPath:'dist/'
   },
   module:{
      rules:[
         {
            test:/\.css$/,
            // css-loader只负责将css文件进行加载
            // style-loader负责将样式添加到DOM中
            // 使用顺序是从右向左
            use:['style-loader','css-loader']
         },
         {
            test: /\.less$/,
            use:['style-loader','css-loader','less-loader'] // 将 Less 编译为 CSS
         },
         {
            test: /\.(png|jpg|gif|jpeg)$/,
            use:[
               {
                  loader:'url-loader',
                  options:{
                     limit:8192,//只处理小于8kb的图片
                     name:'img/[name].[hash:8].[ext]'
                  }
               }
            ] 
         },
         {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,//表示排除(不匹配)这
            use: {
               loader: 'babel-loader',
               options: {
                  presets: ['es2015']
               }
            }
         },
         {
            test:/\.vue$/,
            use:['vue-loader']
         }
      ]
   },
   resolve:{
      alias:{
         'vue$':'vue/dist/vue.esm.js'
      }
   },
   plugins:[
      new webpack.BannerPlugin("本项目版权归wc所有"),
      new HtmlWebpackPlugin({
         template:'index.html'
      }),
      new uglify()//这个在调试中不需要
   ],
   devServer:{//这个在上线时不需要
      contentBase:'./dist',	
      inline:true	
   }

}

我们在项目根目录下创建一个build文件夹,结构如下

  • build
    • base.config.js 存放公共配置
    • pro.config.js 存放上线需要的配置
    • dev.config.js 存放开发时需要的配置

base.config.js

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports={
   entry:'./src/main.js',
   output:{
      path:path.join(__dirname,'./dist'),
      filename:'bundle.js',
      // publicPath:'dist/'
   },
   module:{
      rules:[
         {
            test:/\.css$/,
            // css-loader只负责将css文件进行加载
            // style-loader负责将样式添加到DOM中
            // 使用顺序是从右向左
            use:['style-loader','css-loader']
         },
         {
            test: /\.less$/,
            use:['style-loader','css-loader','less-loader'] // 将 Less 编译为 CSS
         },
         {
            test: /\.(png|jpg|gif|jpeg)$/,
            use:[
               {
                  loader:'url-loader',
                  options:{
                     limit:8192,//只处理小于8kb的图片
                     name:'img/[name].[hash:8].[ext]'
                  }
               }
            ] 
         },
         {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,//表示排除(不匹配)这
            use: {
               loader: 'babel-loader',
               options: {
                  presets: ['es2015']
               }
            }
         },
         {
            test:/\.vue$/,
            use:['vue-loader']
         }
      ]
   },
   resolve:{
      alias:{
         'vue$':'vue/dist/vue.esm.js'
      }
   },
   plugins:[
      new webpack.BannerPlugin("本项目版权归wc所有"),
      new HtmlWebpackPlugin({
         template:'index.html'
      })
   ]
}

pro.config.js

const webpackMerge = require('webpack-merge');
const baseConfig = require('./base.config.js')
module.exports=webpackMerge(baseConfig,{
   devServer:{
      contentBase:'./dist',	//基于dist文件夹
      inline:true	//实时更新
   }
})

dev.config.js

const uglify = require('uglifyjs-webpack-plugin');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./base.config.js')

module.exports=webpackMerge(baseConfig,{
   plugins:[
      new uglify()
   ]
})

分离后

  1. 删除webpack.config.js

  2. 修改package.json

    "scripts": {
       "test": "echo \"Error: no test specified\" && exit 1",
       "build": "webpack --config ./build/pro.config.js",//--config 指定配置文件
       "dev": "webpack-dev-server --open --config ./build/dev.config.js"
    },