webpack入门级篇章,看这篇就够了

序言

     最近由于要准备关于前端的分享课程,不知道要讲些什么,什么最重要,回想了一下,感觉什么都比较重要(哈哈哈),思来想去还是觉得讲一下《webpack自动化打包》,为什么呢,因为webpack如日中天,哈哈,不扯淡了,下面进入正题。

Webpack到底是什么鬼

     官方的概念:webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。通俗点理解就是你前端的东西,然后通过它将其打包为合适的格式以供浏览器使用,它火起来的原因是单页面的应用和js模块化,webpack提供了扩展的机制,在很多社区的支持下,在各个方面都得到了拓展和应用。

Webpack和Gulp、Grunt有什么区别,为什么使用Webpack

     读了上面的,可能会有人问,不是有grunt和gulp的打包工具吗,他们也很好用啊,我觉得很好啊,其实webpack和另外两个没有可比性,不同之处:

     gulp和grunt是一种能够优化前端的开发流程的工具,而webpack是一种模块化的解决方案。

     gulp和grunt工作方式是在一个配置文件中,指明对某一些文件进行编译、组合、压缩等任务的具体步骤,然后它就可以帮助你去完成任务了,最大的问题就是不能不能按需打包,更别说按需加载,如果程序中用不到的文件,也会被打包进来;Webpack的工作方式是将你的整个项目当做一个整体,然后通过给定的一个配置文件,从这个文件开始找到你项目所依赖的文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件,如下图是工作方式:

webpack工作模式

Webpack具有的特性

     1. 对 CommonJS 、 AMD 、ES6的语法做了兼容
     2. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持
     3. 有独立的配置文件webpack.config.js
     4. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间
     5. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活

Webpack 安装及使用

首先全局安装webpack,进入终端就可以安装,安装目录如下:

    npm install -g webpack  //全局安装webpack

在一个本地新建一个文件夹webpackdemo,在终端转到该文件夹下,输入如下命令:

    npm init //这个命令是让你配置一些初始得到信息,例如项目名称、入口文件,作者等

完成上述以后,你的基础信息配置完成,安装局部的webpack,命令如下:

    npm install --save-dev webpack

完成上述步骤,应该会有如下文件:

  • node_modules:依赖包文件
  • package-lock.json:当 node_modules 或 package.json 发生变化时自动生成的文件.这个文件主要功能是确定当前安装的包的依赖,以便后续重新安装的时候生成相同的依赖,而忽略项目开发过程中有些依赖已经发生的更新
  • package.json:刚开始配置的基础信息,以后依赖的安装包名称都会这里

回到之前的那个空的文件夹内,建立如下图所示文件以及文件夹:
文件夹目录

  • app文件夹是我们的基础文件夹

  • public是展示的文件夹

  • webpack.config.js是我们的脚本配置文件

  • hello.js

      //hello.js
      var config = require('../config.json')
    
      module.exports = {
          sayhello: function () {
              console.log(config.world);
          }
      }
  • config.json

      //config.json
      {
          "world": "hello world!"
      }
  • main.js

      //main.js
      var a = require('./hello.js');
    
      a.sayhello();
  • index.html

      //index.html
      <!DOCTYPE html>
      <html lang="en">
    
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <title>webpack</title>
          <script src="bundle.js"></script>
      </head>
    
      <body>
      </body>
    
      </html>
  • bundle.js: 打包以后生成的文件

      开始第一次打包,一般的命令如下:

    webpack app/js/main.js public/bundle.js //webpack 入口文件 输入文件

打包出错

      结果出现了上述红色的错误信息,查明原因是因为我们webpack的版本太高了,使用如下命令:webpack app/js/main.js -o public/bundle.js

打包

      看到如图结果红色的错误信息没有了,出现了黄色的警告信息,经过研究表明,webpack有3中模式,有开发模式(develpoment),生产模式(production),无这三个状态,使用命令webpack app/js/main.js -o public/bundle.js –mode develpoment,如下图所示:

打包

      Hash代表产生的的hash值;version代表目前的webpack的版本;time是打包所用的时间;built at 完成的时间;asseet是产生的文件;size生成文件的大小;chunks是打包的分块;chunk names是打包名称;

      现在看你的项目里面,publick文件夹下面是不是多了一个bundle.js文件,打开一看和我们原来的差不多,是没有压缩的文件,如果用app/js/main.js -o public/bundle.js –modeproduction命令,生成的文件是是压缩过的文件。

      如果进行到了这里,那你就成功了,哈哈,是不是很简单,只要细心分析,肯定可以的

      但是你有没有发现,难道我每次打包处理文件,都要这样去敲命令行,还这么多信息要输入,答案肯定会的,下面我们进行配置webpack.comfig.js(最开始创建文件时已经创建),如下代码:

    module.exports = {
        entry: __dirname + "/app/js/main.js", //唯一入口文件
        output: {
            path: __dirname + "/public", //打包后的文件存放的地方
            filename: "bundle.js" //打包后输出文件的文件名
        }
    }
    //“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录,你可写,也可去掉

      我们运行命令:webpack,如图:

webpack打包

      看到了产生了bundle.js,但是有黄色的信息警告,我们仔细一看是,没有配置模式,打开webpack.config.js,添加代码:

    module.exports = {
        entry: __dirname + "/app/js/main.js", //唯一入口文件
        output: {
            path: __dirname + "/public", //打包后的文件存放的地方
            filename: "bundle.js" //打包后输出文件的文件名
        },
        mode: 'development' // 设置mode
    }

      然后运行webpack,发现了没有黄色的警告信息
      另一种打包方式,配置package.json:


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

      运行命令:npm start ,和上述webpack命令一样

      注:上述的两种打包都是在全局安装的几种情况下打包的 ,非全局打包:

      1. node_modules/ .bin/webpack app/js/main.js public/bundle.js
      2. 配置package.json,运行命令:npm start

      package.json中的script会安装一定顺序寻找命令对应位置,本地的node_modules/.bin路径就在这个寻找清单中,所以无论是全局还是局部安装的Webpack,你都不需要写前面那指明详细的路径了。

强大的调试功能(生成Source Maps)

      开发总是离不开调试,方便的调试能极大的提高开发效率,不过有时候通过打包后的文件,你是不容易找到出错了的地方,对应的你写的代码的位置的,Source Maps就是来帮我们解决这个问题的.

devtool选项 配置结果
source-map 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度;
cheap-module-source-map 在一个单独的文件中生成一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便;
eval-source-map 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项;
cheap-module-eval-source-map 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点;方法构建速度更快,但是不利于

      上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的打包速度的后果就是对打包后的文件的的执行有一定影响。

Webpack搭建本地服务

      Webpack搭建本地的服务器,可以监听你修改的代码,并刷新显示修改后的结果,服务器是基于node.js构建,首先要装它的依赖:

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

      以下是服务器的配置选择,具体点击了解更多

devserver的配置选项 功能描述
contentBase 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public”目录)
port 设置默认监听端口,如果省略,默认为”8080“
inline 设置为true,当源文件改变时会自动刷新页面
historyApiFallback 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html

      在webpack.config.js中配置devserver

    module.exports = {
        devtool: 'eval-source-map',
        entry: __dirname + "/app/js/main.js", //唯一入口文件
        output: {
            path: __dirname + "/public", //打包后的文件存放的地方
            filename: "bundle.js" //打包后输出文件的文件名
        },
        mode: 'development', // 设置mode
        devServer: {
            contentBase: "./public", //本地服务器所加载的页面所在的目录
            historyApiFallback: true, //不跳转
            inline: true //实时刷新
        }
    }

      在package.json中配置script:

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

      在终端输入npm run server ,如下图所示,已经启动成功,并且会打开浏览器:

webpack服务启动

      运行结果:

运行结果

      我们修改一下config.js文件,内容改为I come from china,如下如。终端显示:

终端运行结果

      已经编译完成,生成新的文件bundle.js,然后看看浏览器,如图

浏览器结果

      在控制台已经打印出修改的结果。

Module

      Module 主要是用来配置加载器(Loaders),包括loaders、preLoaders、postLoaders、noParse。webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。

Loaders

      Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置,Loaders的配置包括以下几方面:

      test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
      loader:loader的名称(必须)
      include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
      query:为loaders提供额外的设置选项(可选)

Babel

      Babel其实是一个编译JavaScript的平台,让你能使用最新的JavaScript代码(ES6,ES7…),而不用管新标准是否被当前使用的浏览器完全支持,让你能使用基于JavaScript进行了拓展的语言,比如React的JSX。

      如何安装Babel并且配置

      babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset包和解析JSX的babel-preset-react包)。

      如下命令安装:

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

      配置webpack.config.js

    module.exports = {
        devtool: 'eval-source-map',
        entry: __dirname + "/app/js/main.js", //唯一入口文件
        output: {
            path: __dirname + "/public", //打包后的文件存放的地方
            filename: "bundle.js" //打包后输出文件的文件名
        },
        mode: 'development', // 设置mode
        devServer: {
            contentBase: "./public", //本地服务器所加载的页面所在的目录
            historyApiFallback: true, //不跳转
            inline: true //实时刷新
        },
        module: {
            rules: [{
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "env", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }]
        }
    }

      如上述代码,webapck已允许支持了es6和jsx语法,所以后续我们会用react,所以要安装react需要的配置信息。

      终端安装:

    npm install --save react react-dom

      更改hello.js,返回一个react的组件

    import React,{Component} from 'react'
    import config from './config.json'

    class hello extends Component {
        render() {
            return (
                <div >
                    {config.world}
                </div>
            )
        }
    }
    export default hello

      更改main.js,渲染hello模块

    import React from 'react'
    import {render} from 'react-dom'
    import Hello from './hello'

    render(<Hello />, document.getElementById('root'));

      更改public/index.html,添加

    <div id="root"></div>

运行npm run server,出现了2处错误,第一处:

报错信息

      如上图看出,是版本出现了问题,打开package.json,发现babel-core的版本是6.26.3,凡是babel-loader的版本是8.X,并且提示了如果要用babel-core 的6.X版本,所以要装babel-loader的7.X的版本,所以运行终端:nom install –save-dev babel-loader@7 , 然后在运行npm run server,发现终端没有错误了,但是浏览器报错了,如图:

报错信息

      仔细分析,原来是我们的js引入有问题,我将webpack生成的js文件放在了head,此时DOM还没有建立完毕,因此出现 not a DOM element 的错误,所以将bundle.js文件放在HTML底部就可以了。

      然后运行,发现成功了,没有报错的信息,浏览器所示:

成功信息

      由于Babel在webpack.config.js里配置,考虑到babel具有非常多的配置选项,并且会使webpack.config.js的文件越来越复杂,所以我们单独新建一个文件”.babelrc”,和webpack.config.js在同一级,如图所示更改:

配置信息

CSS模块

      Webpack有两个依赖包来处理样式表(css-loader 和 style-loader),这里我们只引入css文件(不引入sass和less,因为和css引入方式相同),二者的处理方式不同:

  • style-loader:style-loader能够在需要载入的html中创建一个标签,标签里的内容就是CSS内容

  • css-loader:css-loader是允许在js中import一个css文件,会将css文件当成一个模块引入到js文件中

      安装(css-loader和 style-loader)

    npm install --save-dev style-loader css-loader

      在css文件夹中创建main.css,并输入如下内容:

    body {
        margin: 0;
        font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
        font-size: 18px;
        background-color: beige;
    }

    h1, h2, h3, h4, h5, h6, p, ul {
        margin: 0;
        padding: 0;
    }

      在主入口文件(main.js)中导入main.css

    import React from 'react'
    import {render} from 'react-dom'
    import Hello from './hello'

    import '../css/main.css' // 导入css

    render(<Hello />, document.getElementById('root'));

      运行npm start,发现你加入的css文件已经被引入了,并且生效了。

      那这里有出现一个问题,大多数的样式表,都有很多的全局变量名称,每个开发者都有可能写两个或者以上的相同的名称,以后的维护和修改都比较困难,怎么去避免全局变量名的污染等,答案是肯定可以的,引入了css module,有兴趣可以访问官方文档

      Css module技术是把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。Webpack对CSS模块化提供了非常好的支持,只需要在CSS loader中进行简单配置即可,然后就可以直接把CSS的类名传递到组件的代码中,这样做有效避免了全局污染。下面我们进行配置:

    {// 指定启用css modules
        test: /\.css$/,
        use: [
            {
                loader: "style-loader"
            }, {
                loader: "css-loader",
                options: {
                modules: true, // 指定启用css modules
                localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
                }
            }
        ]
    }

      在css文件夹中创建 hello.css,加入如下代码:

    .rootchild{
        font-size: 20px;
        color:red;
    }

      hello.js更改如下:

    import React,{Component} from 'react'
    import config from './config.json'

    import helloone from '../css/hello.css' //引入css

    class hello extends Component {
        render() {
            return (
                <div className={helloone.rootchild}> {/* className 是添加类名 */}
                    {config.world}
                </div>
            )
        }
    }
    export default hello

      运行npm start,如图所示:发现字体的颜色发生了变化,并且类名也发生了变化,这样相同的类名也不会造成不同组件之间的污染。

css组件

插件学习(Plugins)

      插件是webpack的拓展功能,作用于整个构建的过程,很多人把loader和插件分不清楚,你只要记住,loader是处理源文件的,一次处理一个,而插件不处理单个文件,是处理整个项目的构成,webpack有很多的内置插件,也有很多的第三方插件,如果是第三方,是需要手动去安装,再引用

插件

      红色的部分是添加的部分,如图打包以后:

打包产生

HtmlWebpackPlugin

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

      先对原来的文件夹进行一些改动:移除public文件夹,在app文件夹下创建一个index.tmpl.html的文件模板,在使用插件的时候,会自动依据模板生成一个新的index.html。Index.tmpl.html 模板如下

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>webpack</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
    </html>

      安装HtmlWebpackPlugin

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

      更新配置(webpack.config.js):

    const webpack = require('webpack');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
        devtool: 'eval-source-map',
        entry: __dirname + "/app/js/main.js", //唯一入口文件
        output: {
            path: __dirname + "/build", //打包后的文件存放的地方
            filename: "bundle.js" //打包后输出文件的文件名
        },
        mode: 'development', // 设置mode
        devServer: {
            contentBase: __dirname + './build', //本地服务器所加载的页面所在的目录
            historyApiFallback: true, //不跳转
            inline: true //实时刷新
        },
        module: {
            rules: [{
                    test: /(\.jsx|\.js)$/,
                    use: {
                        loader: "babel-loader"
                    },
                    exclude: /node_modules/
                },
                {
                    test: /\.css$/,
                    use: [{
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true, // 指定启用css modules
                            localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
                        }
                    }]
                }
            ]
        },
        plugins: [
            new webpack.BannerPlugin('版权所有,翻版必究'),
            new HtmlWebpackPlugin({
                template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数
            })
        ]
    }

      引入插件名称,修改输出地址,在plugins里添加,运行npm start

      发现文件夹里多了一个build的文件夹,打开文件,发现有一个index.html和build.js文件。用浏览器打开index.html,发现和原来public文件夹里的事一样的,这样打包更加的方便。
      如果你要实时更新你的代码,一定要配置好服务的内容devServer。

优化插件

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

  • OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
  • UglifyJsPlugin:压缩JS代码,这个因为高版本的需求,已经不用这个插件了(下面的配置中,我会配置)
  • ExtractTextPlugin:分离CSS和JS文件

      由于第一个和第二个是内置插件,只要引入就可以了,但是后面分离css和js,需要安装插件:

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

      为什么要加@next,因为extract-text-webpack-plugin目前版本不支持webpack4,下面是配置了组件分配Id,压缩js,分离了css和js:

    const webpack = require('webpack');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const ExtractTextPlugin = require('extract-text-webpack-plugin');
    module.exports = {
        devtool: 'eval-source-map',
        entry: __dirname + "/app/js/main.js", //唯一入口文件
        output: {
            path: __dirname + "/build", //打包后的文件存放的地方
            filename: "bundle.js" //打包后输出文件的文件名
        },
        mode: 'development', // 设置mode
        devServer: {
            contentBase: __dirname + './build', //本地服务器所加载的页面所在的目录
            historyApiFallback: true, //不跳转
            inline: true //实时刷新
        },
        module: {
            rules: [{
                    test: /(\.jsx|\.js)$/,
                    use: {
                        loader: "babel-loader"
                    },
                    exclude: /node_modules/
                },
                {
                    test: /\.css$/,
                    use: ExtractTextPlugin.extract({
                        fallback: "style-loader",
                        use: [{
                            loader: "css-loader",
                            options: {
                                modules: true,
                                localIdentName: '[name]__[local]--[hash:base64:5]'
                            }
                        }]
                    })
                }
            ]
        },
        plugins: [
            new webpack.BannerPlugin('版权所有,翻版必究'),
            new HtmlWebpackPlugin({
                template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数
            }),
            new webpack.optimize.OccurrenceOrderPlugin(),
            new ExtractTextPlugin("style.css")
        ],
        /* 压缩js代码 */
        optimization: {
            minimize: true
        }
    }
    //“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。

      运行npm start ,发现build文件夹里多出了style.css。

引入图片

      首先还是要装插件,终端:

    npm install --save-dev url-loader

Hello.css

    .rootchild{
        font-size: 20px;
        color:red;
        background-image: url('../img/hello.jpg');
        width: 197px;
        height: 197px;
    }

Webpack.config.js

    {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader?limit=8192&name=img/[hash:8].[name].[ext]'
    }

      运行代码:npm start ,浏览器打开build文件夹下的index.html,如图:

图片展示

结束语

      写完这篇文章,想起当时搞这个基础配置,也花费了几天的时间,总是搞不好,版本的问题,配置问题,反正各种问题,也是各个博客去看自己错在什么地方,搞的人头都大了,总之当时功夫不负有心人,当时是成功了。这篇文章是比较基础的,对于新手入门还是很重要,只要按照流程,还是很容易的搭建出来,文章里有很多容易出错的点,我也列举出来了,对于点的介绍都很全面,如果看到这里,说明你已经基本入门了,可以很好地去了解其他关于webpack的知识点。

本文的代码在这里webpackbasic