关于webpack

webpakc的是模块打包器.而不是任务执行器。

它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
webpack也提供了便捷的打包流程,项目构建,插件管理等等.
为更好的构建项目,从开发到生产都一一提供了解决方案.
Vue官方也推荐使用的vue-loader也是基于webpack的.

WebPack和Grunt以及Gulp相比有什么特性

Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。

Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

Webpack的处理速度更快更直接,能打包更多不同类型的文件。

一个常见的webpack配置文件


webpack.config.js

 

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

  module.exports = {
    entry: {//唯一入口文件路径
      app: './src/index.js'
    },
    output: {
      //publicPath:XXX 可以资金定义路径,build后输出的内容将会放入到这个路径当中
      filename: '[name]-[hash].js', //打包后输出文件的文件名,注意name用方括号括起来,name的值对应的为entry的key值,这样为了区分多个文件 
      path: path.resolve(__dirname, 'dist')//打包后的文件存放的地方(__dirname指向当前执行脚本所在的目录)
    },
    devtool: 'eval-source-map',//Source Maps提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试,强调你只应该开发阶段使用它
    devServer: {//本地开发服务器,浏览器监听你的代码的修改,并自动刷新显示修改后的结果
      contentBase: './dist',//本地服务器所加载的页面所在的目录
      historyApiFallback: true, //不跳转,所有的跳转将指向index.html
      inline: true,//实时刷新
      hot: true
    },
    module: {//Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置
     rules: [
       {
         test: /\.css$/,//一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
         use: [
             {
                loader: "style-loader"
            }, {
                loader: "css-loader",
                options: {
                    modules: true, //通过CSS模块,所有的类名,动画名默认都只作用于当前模块。 指定启用css modules,把CSS的类名传递到组件的代码中,这样做有效避免了全局污染
                    localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
                }
         ]//css-loader使你能够使用类似@import 和 url(...)的方法实现 require()的功能,style-loader插件使对于CSS文件进行实时渲染到页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。
       },
       {
         test: /(\.jsx|\.js)$/,
         use: {
            loader: "babel-loader",//loader的名称(必须)
            options: {//为loaders提供额外的设置选项(可选)
                        presets: [
                            "env", "react"//解析Es6的babel-env-preset包和解析JSX的babel-preset-react包
                        ]
                    }
         },
         exclude: /node_modules/  //include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
        },
        {
         test: /\.css$/,
         use: ExtractTextPlugin.extract({
            fallback: "style-loader",
            use: [{
                loader: "css-loader",
                options: {
                    modules: true,
                    localIdentName: '[name]__[local]--[hash:base64:5]'
                }
            },
            {
                loader: "postcss-loader"//自动添加适应不同浏览器的css前缀
            }],
         })
        }
       
     ]
    },
    plugins: [
      new CleanWebpackPlugin(['dist']),//清除dist目录
      new HtmlWebpackPlugin({ //依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。
        title: 'Hot Module Replacement'
      }),
      new webpack.NamedModulesPlugin(),//添加该插件更容易观察依赖文件被更新
      new webpack.HotModuleReplacementPlugin(),//动态替换文件,热加载插件
      new webpack.BannerPlugin('版权所有,翻版必究'),//版本申请插件
      new webpack.optimize.CommonsChunkPlugin({
        name: 'common' // Specify the common bundle's name.
      }),
      new webpack.HashedModuleIdsPlugin(),//
      new webpack.optimize.OccurrenceOrderPlugin(),//为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
      new webpack.optimize.UglifyJsPlugin(),//压缩JS代码
      new ExtractTextPlugin("style.css")//分离CSS和JS文件
    ],
    
  };

 

 

package.json

{
  "name": "webpack demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack",
    "server": "webpack-dev-server --open",
    "build": "NODE_ENV=production webpack --config ./webpack.prod.js --progress"
  },
  "author": "",
  "license": "ISC","dependencies": {

    "swiper": "^4.0.7",
    "vue": "^2.5.2",
    "vue-router": "^2.8.1",
    "element-ui": "2.2.1",

"react": "^15.6.1",
     "react-dom": "^15.6.1"
  }
}

 

使用webpack

1. 安装

全局安装:

//全局安装
npm install -g webpack

局部安装:

//局部安装
npm install --save-dev webpack

2.工程结构

可以根据项目的实际的情况设置目录。
一般情况下可以设置为源码目录,生产目录。

目录结构如下:

 

3. 写一个webpack.config.js配置文件,首先是入口文件路径(entry)和打包后文件的存放路径(output)。

module.exports = {
  entry:  __dirname + "/src/main.js",//已多次提及的唯一入口文件
  output: {
    path: __dirname + "/dist",//打包后的文件存放的地方
    filename: "bundle.js"//打包后输出文件的文件名
}

4. 对npm进行配置后可以在命令行中使用简单的npm start命令来执行打包任务。在package.json中对scripts对象进行相关设置即可,package.json中的script会按照一定顺序寻找命令对应位置,具体设置方法如下:

{
  "name": "webpack-sample-project",
  "version": "1.0.0",
  "description": "Sample webpack project",
  "scripts": {
    "start": "webpack" 
  },
  "author": "zhang",
  "license": "ISC",
  "devDependencies": {
    "webpack": "3.10.0"
  }
}

注意:

npm的start命令是一个特殊的脚本名称,其特殊性表现在,在命令行中使用npm start就可以执行其对于的命令。如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name},npm run build

5. 开发工具

webpack 提供了强大的开发工具

webpack-server 提供了访问页面的服务

webpack-watch 提供了观察文件的变化 

1> source maps

开发总是离不开调试,方便的调试能极大的提高开发效率,不过有时候通过打包后的文件,你是不容易找到出错了的地方,对应的你写的代码的位置的。而webpack可以在打包时为我们生成source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试。

webpack的配置文件中配置source maps,需要配置devtool。

对小到中型的项目中,eval-source-map是一个很好的选项,再次强调,你只应该开发阶段使用它。

module.exports = {
  devtool: 'eval-source-map',//source maps,便于调试
  entry:  __dirname + "/src/main.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  }
}

2>使用webpack构建本地服务器

为webpack打包生成的文件提供web服务,让你的浏览器监听你的代码的修改,并自动刷新显示修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖。

npm install --save-dev webpack-dev-server

webpack.config.js配置文件如下:

module.exports = {
  devtool: 'eval-source-map',

  entry:  __dirname + "/src/main.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },

  devServer: {
    contentBase: "./dist",//设置本地服务器访问的基本目录
    historyApiFallback: true,//不跳转
    inline: true//实时刷新
  } 
}

package.json中的scripts对象中添加如下命令,用以开启本地服务器:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack",
    "server": "webpack-dev-server --open"
  },

在终端中输入npm run server,即可在本地的8080端口查看结果

6. Loaders

Loaderswebpack提供的最激动人心的功能之一。通过使用不同的loaderwebpack有能力调用外部的脚本或工具,实现对不同格式的文件的处理,比如说分析转换scss为css,或者把下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件,对React的开发而言,合适的Loaders可以把React的中用到的JSX文件转换为JS文件。
 
Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置。
 
Loaders的配置包括以下几方面:
  • test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
  • use: 进行转换时,应该使用哪个loader
  • loader:loader的名称(必须)
  • include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
  • query:为loaders提供额外的设置选项(可选)
1> css
webpack提供两个工具处理样式表,css-loader和 style-loader,二者处理的任务不同。
css-loader使你能够使用类似@import和 url(...)的方法实现 require()的功能;
style-loader将所有的计算后的样式加入页面中;
二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

安装style-oader、css-loader,loader的作用就是将文件进行处理(编译,压缩)

npm install --save-dev style-loader css-loader
module.exports =     
{
    module: {
      rules: [
        {
          test: /\.css$/, //指定匹配文件,使用style-loader,css-loader
          use: [
            'style-loader',
            'css-loader'
          ]//引入的顺序至关重要,不可改变
      }]
     }
}

css预处理器

Sass 和 Less 之类的预处理器是对原生CSS的拓展,它们允许你使用类似于variablesnestingmixinsinheritance等不存在于CSS中的特性来写CSS,CSS预处理器可以这些特殊类型的语句转化为浏览器可识别的CSS语句。

以下是常用的CSS 处理loaders:

  • Less Loader
  • Sass Loader
  • Stylus Loader

-PostCSS

使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀。

首先安装postcss-loader 和 autoprefixer(自动添加前缀的插件)

npm install --save-dev postcss-loader autoprefixer

在webpack配置文件中添加postcss-loader,在根目录新建postcss.config.js。

//webpack.config.js
module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    }
}

postcss.config.js

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}

2> babel

Babel其实是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的:

  • 让你能使用最新的JavaScript代码(ES6,ES7...),而不用管新标准是否被当前使用的浏览器完全支持;
  • 让你能使用基于JavaScript进行了拓展的语言,比如React的JSX;

babel转化语法所需依赖项:

babel-loader: 负责es6语法转化

babel-core: babel核心包

babel-preset-env: 告诉babel使用哪种转码规则进行文件处理

 

先来一次性安装依赖包 

// npm一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react

webpack中配置Babel的方法如下:

module.exports = {
    ...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "env", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }
        ]
    }
};
Babel其实可以完全在 webpack.config.js中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。我们现在的babel的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc里的babel配置选项),如下:
 
webpack.config.js
module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: "babel-loader",
                exclude: /node_modules/
            }
        ]
}

.babelrc

{
  "presets": ["@babel/preset-env"]
}

 

3> Vue-Loader

.vue 文件是一个自定义的文件类型,用类 HTML 语法描述一个 Vue 组件。每个 .vue 文件包含三种类型的顶级语言块 <template><script> 和 <style>,还允许添加可选的自定义块:

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

<custom1>
  This could be e.g. documentation for the component.
</custom1>

vue-loader 会解析文件,提取每个语言块,如有必要会通过其它 loader 处理,最后将他们组装成一个 CommonJS 模块,module.exports 出一个 Vue.js 组件对象。

vue-loader 支持使用非默认语言,比如 CSS 预处理器,预编译的 HTML 模版语言,通过设置语言块的 lang 属性。例如,你可以像下面这样使用 Sass 语法编写样式:

<style lang="sass">
  /* write Sass! */
</style>

 

7. 插件(Plugins)

插件(Plugins)用来拓展webpack功能,他们会在整个构建过程中生效,执行相关的任务。

loaders是在打包构建过程中用来处理源文件的(JSX,Scss, Less),一次处理一个。

plugins并不直接操作单个文件,是对整个构建过程起作用。

 

使用插件的方法:

使用某个插件,需要在webpack配置中的plugins关键字部分添加该插件的一个实例(plugins是一个数组)。

例如:添加了一个给打包后代码添加版本声明的插件

const webpack = require('webpack');

module.exports = {
...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究')
    ],
};

 

下面推荐几个常用的插件:

HtmlWebpackPlugin

依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。

安装:

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

webpack.config.js:

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

module.exports = {
    ...
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究'),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数
        })
    ],
};

CleanWebpackPlugin

清除目录的插件。

添加了hash之后,会导致改变文件内容后重新打包时,文件名不同而内容越来越多,因此这里介绍另外一个很好用的插件clean-webpack-plugin

引入clean-webpack-plugin插件后在配置文件的plugins中做相应配置即可:

 

安装:

npm install clean-webpack-plugin --save-dev 

webpack.config.js:

整个配置文件,注意插件的配置顺序.

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

  module.exports = {
    ...
    plugins: [
      new CleanWebpackPlugin(['dist']),//清除dist目录
      new HtmlWebpackPlugin({ //生成一个html页面指定其title,然后会自动将js文件生成引入js代码
        title: 'Output Management'
      })
    ]
  };

 

Hot Module Replacement (HMR)

主要是启动服务后文件可以进行热更新,例如css样式或者js等文件变更,会自动更新到页面上.只能用于开发环境不能用于生产环境.

它允许你在修改组件代码后,自动刷新实时预览修改后的效果。

在webpack中实现HMR也很简单,只需要做两项配置

  1. 在webpack配置文件中添加HMR插件;
  2. 在Webpack Dev Server中添加“hot”参数;

webpack.config.js

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports
= { ... devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true, hot: true }, ... plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数 }), new webpack.NamedModulesPlugin(),//添加该插件更容易观察依赖文件被更新 new webpack.HotModuleReplacementPlugin()//热加载插件 ], };

目前为止,我们已经使用webpack构建了一个完整的开发环境。

产品阶段的构建

在产品阶段,可能还需要对打包的文件进行额外的处理,比如说优化,压缩,缓存以及分离CSS和JS。

对于复杂的项目来说,需要复杂的配置,这时候分解配置文件为多个小的文件可以使得事情井井有条。因此最好添加如下配置到配置文件的入口.

process.env.NODE_ENV = 'production'

webpack 配置可以提炼出来,使用webpack-merge将webpack的配置文件进行合并。

我们可以建一个webpack.common.js文件,然后创建一个webpack.prod.js,webpack.dev.js,那么可以将prod,dev引入common共同引用参数.

common文件

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

 module.exports = {
   entry: {
     app: './src/index.js'
   },
   plugins: [
     new CleanWebpackPlugin(['dist']),
     new HtmlWebpackPlugin({
       title: 'Production'
     })
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist')
   }
 };

生产环境:

webpack提供了一些在发布阶段非常有用的优化插件,它们大多来自于webpack社区,可以通过npm安装,通过以下插件可以完成产品发布阶段所需的功能

  • OccurenceOrderPlugin:为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
  • UglifyJsPlugin:压缩JS代码;
  • ExtractTextPlugin:分离CSS和JS文件(将css引入到js文件中,会一起打包成js文件)

OccurenceOrder 和 UglifyJS plugins 都是内置插件,你需要做的只是安装其它非内置插件

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

webpack.prod.js

 const merge = require('webpack-merge');
 const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
 const ExtractTextPlugin = require('extract-text-webpack-plugin');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
   plugins: [
     new webpack.BannerPlugin('版权所有,翻版必究'),
     new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html"
     }),
     new webpack.optimize.OccurrenceOrderPlugin(),
     new webpack.optimize.UglifyJsPlugin(),
     new ExtractTextPlugin("style.css")
   ]
 });

开发环境:

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

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

 

 8. 代码管理

默认的情况下,webpack会把引用的文件都会build到单独的文件当中.我们可能会把一些第三方的模块放到一个单独文件里.

需要对配置文件做如下改动:

     //plugins数组当中添加一下参数
     new webpack.optimize.CommonsChunkPlugin({
       name: 'common' // Specify the common bundle's name.
     })

9. 垫片

当我们需要全局引入一个变量,我们可以使用ProvidePlugin。(webpack不推荐这样做,因为会影响到模块化编程)

每个js文件就不需要去import引入对应的代码

plugins: [
      new webpack.ProvidePlugin({
       _: 'lodash'
      })
]

或许只需要部分第三方库的功能,我们可以这样进行配置

plugins: [
      new webpack.ProvidePlugin({
       _: 'lodash',
       join:['lodash', 'join']//我们可以这样进行配置 ['module', 'child', 'child']
      })
]

10. 缓存

缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变)

webpack可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前。

module.exports = {
..
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: "bundle-[hash].js"
    },
   ...
};

还有个问题webpack默认的hash是根据module.id以及内容生成,而module.id根据解析文件的顺序生成,一个重要的问题是每次改动引入的文件,就可能会造成其它文件hahs不一致.

这时候就需要HashedModuleIdsPlugin插件,指定使用Path以及内容作为hash的内容.

只需要配置文件当中在plugins增加如下配置:

plugins:[
    ...,
    new webpack.HashedModuleIdsPlugin(),
    ...
]


 本文根据https://www.jianshu.com/p/42e11515c10f改写,加了一些自己的理解,希望对大家有帮助。