Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack文档高级配置 #40

Open
huangchucai opened this issue Oct 13, 2017 · 0 comments
Open

webpack文档高级配置 #40

huangchucai opened this issue Oct 13, 2017 · 0 comments

Comments

@huangchucai
Copy link
Owner

webpack文档高级配置

主要从下面几个方面讲解:

  1. webpack的生产配置环境
  2. webpack的优化机制
    • 模块分离
    • 按需加载
    • 第三方模块利用缓存
  3. webpack的部分插件

webpack的生产配置构建

在开发一个项目的时候,我们往往需要几个环境的切换,例如:开发(dev),生产(production), 测试(test),这里主要讲解开发和生产中的环境配置,webpack官方推荐我们将公共的基本配置放在一起,然后对开发和生产进行不同的配置文件,通过官方提供的插件webpack-merge进行配置的合并,然后通过webpack编译生产。

webpack.common.js // 通用文件

webpack.dev.js // 开发文件

webpack.prod.js // 生产文件

// 通用文件 包含生产和开发的一些基本的使用 webpack.common.js
module.exports = {
  entry: {
    app: './src/index.js',
    vendor: ['lodash']
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: '[name][chunkhash:3].bundle.js',
    chunkFilename: '[name].bundle.js',
    // publicPath: '/'
  },
  plugins: [
    new htmlWebpackPlugin({
      title: 'prodution' 
    }),
    new cleanWebpackPlugin(['dist'])
  ]
}
// 开发文件  在通用文件下面进行拓展,合并并替换个性化的配置 webpack.dev.js
let merge = require('webpack-merge');
let commonConfig =  require('./webpack.common.js');

module.exports = merge(commonConfig, {
    // devtool: 'inline-source-map',
  devtool: 'source-map',
  devServer: {
    contentBase: './dist'
  },
})

// 生产文件   在通用文件下面进行拓展,合并并替换个性化的配置 webpack.prod.js
let merge = require('webpack-merge');
let webpack = require('webpack')
let commonConfig = require('./webpack.common.js');

module.exports = merge(commonConfig, {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      // 利用manifest来进行缓存
      // names: ['vendor', 'manifest'],
      name: 'vendor',
      minChunks: function(module, count) {
        console.log(module.resource)
        console.log(count)
      }
    }),
  ]
})

生产环境使用环境变量

当然这些只是配置文件,如果我们想在配置文件中使用环境变量process.env.NODE_ENV)进行不同情况下面的判定,我们可以通过npm script进行环境变量的配置。windows下要获取环境变量需要使用一个包插件cross-env

// package.json
{
   "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "cross-env NODE_ENV=production node_modules/.bin/webpack --config webpack.prod.js",
    "dev": "cross-env NODE_ENV=development webpack --config webpack.dev.js",
    "dev": "webpack-dev-server --open --config webpack.dev.js  NODE_ENV=production",  // 不能获取环境变量
    "server": "cross-env NODE_ENV=production node server.js"
  }, 
}
 
// 在配置文件中,我们可以通过process.env.NODE_ENV来获取环境变量 
// webpack.prod.js
if(process.env.node_ENV){
  console.log(111)
}
console.log(process.env.NODE_ENV)

一般情况下,我们已经区分了不同的环境下面的不同的webpack配置文件,所以很少通过这样配置环境变量,但是更多的是,我们在源文件中通过不同的环境参数进行不同的判定。但是当开启的是webpack-dev-server --open的时候,不能获取环境变量。

源文件中使用环境变量

webpack提供自带插件webpack.DefinePlugin()来给源文件来配置环境变量。

module.exports = {
  new webpack.DefinePlugin({
    'process.env' : {
      'NODE_ENV': JSON.stringify('development')
    }
  })
}

// src下的index.js
if (process.env.NODE_ENV !== 'production') {
  console.log('Looks like we are in development mode!');
}

这样我可以在源文件中根据不同的环境变量来编写不同的代码,也可以通过环境变量来提示用户信息。


webpack的优化机制

由于JS模块化的的兴起,前端代码从以前的一个大文件变成了一块块的代码,对于每一个模块的引用和使用就是一个问题了,总结由于模块化需要注意的问题。

  1. 对于重复引用的问题,一个模块重复引用,加大文件大小。
  2. 自己内部代码和第三方代码的分离问题
  3. 对于很大的第三方模块代码,一般都不会有很大的改变的情况下,我们能不能利用浏览器缓存技术?
  4. 我能不能按需加载,就是我什么页面需要什么模块就加载什么模块,减轻首页加载的负担?

带着上面的四个问题,我们来探究一下webpack的优化机制。

1. 解决模块重复引用的问题

对于**多个入口chunk**的重复应用一个模块的情况下。

现在我们有三个模块a.js, b.js index.js

//a.js
export default function() {
  console.log(1)
}

// b.js
import a from 'a.js'
a()

// index.js 
import a from 'a.js'
a()
console.log('This ia index.js')

对于模块a.js, 我们需要在其他的2个模块中使用,webpack打包的时候,就会重复打包a.js, 这样就加大了文件的大小和负担,有没有一种方法可以提前把a.js 存放起来,当我们在使用的时候,直接去调用就行了。webpack官方也考虑了这种情况,提供了一个自带的插件webpack.optimize.CommonsChunkPlugin来处理这种情况。注意的是,options.name不要和entry的name重合,不然会被认为第三方模块被分离。

const webpack = require('webpack');

module.exports = {
  entry: {
    app: './index.js',
    b: './b.js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: number|fun|Infinity,
    })
  ]
}
// name是重复引用的模块集合输出的文件名
// minChunks: 对于模块被引用的次数,对于小于次数的模块不会被打包进入,虽然还是会生成common.js文件,但是由于小于次数,不会被打包,所有还是像之前的那样重复的引用,加大大小。
// 当minChunks = Infinity的时候,会马上生成 公共chunk,但里面没有模块。利用缓存的时候可以用到

2. 解决内部模块和第三方模块的分离

由于模块化的好处,我们可以很轻松的使用第三方别人写好的模块,加速我们的代码编写,但是也不方便我们进行维护和进一步的拓展,所有有必要把第三方模块和自己的模块进行分离。CommonsChunkPlugin()提供了我们分离第三方模块的使用。通过entry里面指定对于的name,然后分离代码。

module.exports = {
  entry: {
    app: './index.js',
    vendor: ['lodash', 'axios']
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: '[name][chunkhash:3].bundle.js',
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity
      // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块。
    })
  ]
}

3. 利用缓存技术处理第三方修改很少的模块

我们一般在写输出文件的时候,都会利用hash值,让客户端(浏览器)实时更新我们的修改代码,不会走缓存,但是当使用第三方修改很少的模块的时候,我们恰恰需要利用浏览器的缓存,来减少服务器请求,优化页面。webpack一般使用chunkhash来改变文件的hash

按照上面的写法,当我们npm run build的时候,会出现这样的结果,浏览器请求这些js文件的时候
1

我们想的是,可以通过某个技术然后让我们在修改内部代码的时候,vendor[hash].build.js的值不改变,这样浏览器就可以利用缓存,不用重复请求了。

CommonsChunkPlugin 有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。通过指定 entry 配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中。然后就可以通过manifest内部机制,跳过vendor的hash值的改变。

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      //利用manifest来进行缓存
      names: ['vendor', 'manifest']
    })
  ]
}
// 也可以分开写,但是manifest的必须写在vendor的后面
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    })
    new webpack.optimize.CommonsChunkPlugin({
      // name只是一个打包后的文件名,这里实际提取的manifest
      name: 'runtime'
    })
  ]
}

这样配置了后,我们再次修改后,第三方模块的文件名是不会更新的,这样我们可以只修改我们内部代码,然后重新打包后,可以不改变第三方模块的打包文件名,很好的利用浏览器的缓存。

4. 按需加载,减少首页的文件请求压力

浏览器加载首页的时候,如果我们把js文件全部都已经打包好,并且全部放在首页加载的话,就会增大请求,从而首页加载的时间也会增加。可不可以我们点击某个按钮或者交互的时候,加载我们需要的部分,而不是在首页全部加载,这样就可以是实现按需加载,从而减少首页请求服务器和时间。

webpack提供2中方式实现按需加载

  • ES6 的import加载 (官网推荐使用这种方式加载)

import()加载模块后返回一个promise,得到一个对象,其中default属性会是加载模块导出的内容。注释的webpackChunkName:name配合output.chunkFilename:[name].js会变成打包后的文件名。

// index.js
function get() {
  button.onclick = e => import(/* webpackChunkName: "print" */ './print').then((module) => {
     var print = module.default;
     print();
  });
}

// webpack config 
module.exports = {
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: '[name][chunkhash:3].bundle.js',
    chunkFilename: '[name].bundle.js',
  },
}

default

交互点击后:我们发现才加载已经打包好了的print.bundle.js
default

  • webpack自带的require.ensure(dependencies, callback,filename)
//print.js
export default () => {
  console.log('This is a print')
}
// index.js
function get() {
  // print.js里面没有依赖,有依赖的话需要写在[]中。
  button.onclick = e => require.ensure([],() => {
    let print = require('print');
    print()
  },'print');
}
总结:

import方式和require.ensure决定了什么时候加载打包后的模块,webpackChunkName:namefilename配合webpack的输出配置output.chunkFilename控制文件打包后的文件名。


webpack的部分插件

上面的部分我们已经使用了很多的插件,这里总结一下一些常见的插件plugins

webpack通过options.plugins: []来配置相应的插件特性。

webpack自带插件

CommonsChunksPlugin插件

使用方法:

const webpack = require('webpack')
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      .....
    })
  ]
}

使用用途:

  1. 对于多入口chunk,打包重复被引用的模块。
  2. 分离第三方模块

DefinePlugin插件

在使用DefinePlugin之后,我们可以在源文件内部是用配置好的全局环境变量,为什么这样做呢?可以区分开发模式和发布模式。用过node的同学知道,process.env可以获取环境变量。这里沿用了node的。注意区分构建文件webpack.config.js和源文件index.js

使用方法:

const webpack = require('webpack')
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env' : { 'NODE_ENV' : JSON.stringify('production') }
    })
  ]
}
// 字符串必须使用'"xx"'这样的形式, 一般都是用JSON.stringify()来转换

使用用途:

  1. 区分开发模式和发布模式
  2. 显示一些版本和一些实用技术

providePlugin插件

提供全局变量,我们不用在全局的去引用这个全局变量。webpack会自动记载模块。

使用方法:

const webpack = require('webpack')
module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      _map: ['lodash', 'map'],
      Vue: ['vue/dist/vue.esm.js', 'default']
    })
  ]
}

使用说明:

  1. 任何时候,使用$的时候,webpack都会自动加载模块jquery,并且$会被jquery的输出的内容所赋值。
  2. 对于es6的export default必须指定模块的 default 属性。例如: Vue
  3. 可以只赋值模块导出的一部分。 例如:lodash

其他插件

extractTextWebpackPlugin插件

用于把css内部样式提取成css单独文件。对于复杂的样式很有用。

使用方法:

npm install --save-dev extract-text-webpack-plugin

const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
  plugins: [
    new ExtractTextPlugin('filename')
  ]
  module: {
    rules: [
      {
        test: /\.css$/,
  		use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
  		  use: "css-loader"	
  		})
      }
    ]
  }
}

详细查看webpack插件

html-webpack-plugin插件

用于生成单独的html文件,并自动引入打包后的文件。如果有多个入口文件,也会生成多个index.html文件

使用方法:

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

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: 'plugins'
    }) 
  ]
}
// 内部可以配置文件标题,文件名,文件模板等。。。。

详细查看webpack插件

UglifyjsWebpackPlugin

可以在webpack打包后,压缩文件,减少文件的大小。

使用方法:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
  plugins: [
    new UglifyJSPlugin()
  ]
}

使用说明:

  1. 可以配置压缩的文件include exclude test
  2. source map 的配置,映射错误信息。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant