一次webpack体验

4,119 阅读8分钟

目录结构

    project
        - css
            - bootstrap.min.css
            - jb.css
        - fonts
            - 一些bootstrap的字体
        - images
            - 一些项目用到的图片
        - js
            - bootstrap.min.js
            - jquery.min.js
            - jb.js
        - index.html
        - favicon.ico

项目背景

这个是公司要做的一个官方网站。由于项目比较简单,要求是单页的,没有页面跳转,所以只有一个 .html 文件。项目用了比较常规的 bootstrap + jquery 的开发,这个也没啥好说的。考虑到 CDN 的可控性,所以把所有 bootstrap 的资源都下载到了本地进行引用。项目开始时是用了常规的 js 和 css 引用(css 放前面,js 放后面),在开发完成后,发现有时间多余,就考虑用 webpack 对他进行处理一下,以巩固和学习一下 webpack 所用到的知识

初始化工作

1. 初始化

    npm init

先初始化一个 package.json 文件来管理我们 webpack 所依赖的文件包。一路无脑回车即可。

2. 安装 webpack

想要用 webpack ,那么你首先肯定要安装 webpack 才可以啊。用以下命令:

    npm install webpack --save

3. webpack.config.js

在根目录下新建一个 webpack.config.js 文件,用来对 webpack 进行配置。
当有这个文件后,我们系可以在命令行中用以下命令来启动配置好的 webpack 了。

    webpack --config webpack.config.js

4. 修改 webpack 打包命令

以上虽然也可以启动配置好的 webpack。但是每次要输这么一串命令好像有点太长了(懒啊)。所以在 package.json 中修改这样一项:

    "script": {

      + "start": "webpack --config webpack.config.js",

        "test": "echo \"Error: no test specified\" && exit 1"
    }

这样的话我们就可以在命令行中少敲几个键盘了。直接用以下命令,就等同于上面的命令了:

    npm start

好了,到这里,初始化工作就做完了,那么开始我们的 webpack 配置吧

webpack.config.js

1. 哪里来的入口文件?

什么事入口文件

webpack 创建应用程序所有依赖的关系图(dependency graph)。图的起点被称之为入口起点(entry point)。入口起点告诉 webpack 从哪里开始,并根据依赖关系图确定需要打包的内容。可以将应用程序的入口起点认为是根上下文(contextual root) 或 app 第一个启动文件

跟其他的 spa 应用不一样,这样的普通应用,其所有依赖都来自于 index.html 文件。如果说要有入口文件的话,怎么也应该是 index.html 他本身吧。但是 webpack
基本都是用 .js 文件作为入口文件,用 .html 作为入口文件的...(反正我是没有见到过)。至于这里有啥原因的话,大家就参考一下这篇文章吧。(其实我也不懂)

所以说,不管怎么样,我们都需要有个入口文件。

那就不管怎么样,我们经常看到的 webpack 的配置都是这样的

    module.exports = {
        entry: './index.js'
    }

那么我们不管三七二十一,先在根目录下新建一个 index.js,然后让他作为我们的入口文件。

index.js

要知道,对于我们原来的项目而言,我们根本就是不需要这么一个 index.js 文件的(没有他,我们可以活得更好)。但是我们又不得不创建了这样一个文件。那么问题来了,这么创建出来的文件,里面又该放什么内容呢?我们总不该救这么创建一个空文件就算了吧。

在考虑这个问题的时候,我们可以先去看下 webpack 官方的那个很经典的图(我很懒,就不放图了,大家自己去网上找吧)。webpack 把左边乱七八糟的 .js .css .png .jpg .sass。。。等等文件全部打包成了静态资源。也就是说,webpack 打包的是除 html 外的所有资源,那么我们是不是只要把这些资源都放到入口文件中那么就可以让 webpack 帮我们打包了呢?

但是等等。其他的都没有问题,什么 css 啊,什么 js 啊,都好说,因为用来也不过这么几个,但是图片呢,字体呢?我在项目中用了那么多图片,要全部再在 index.js 里面再写一遍!天呐!这是要命的啊!那么我能不能偷懒下,就只写 css 和 js 呢,其他乱七八糟的我先不管?那就先这么来吧。修改我们的 index.js 文件,添加以下内容。

    require('./css/bootstrap.min.css')
    require('./css/jubang.css')
    require('./js/jquery.min.js')
    require('./js/bootstrap.min.js')
    require('./js/jb.js')

先这样把他当作我们的入口文件吧

2. 配置出口文件

出口文件就很好配置了,将他打包到根目录下的 dist 目录中。嗯 ~ 这很常见!

    var path = require('path');
    module.exports = {
        entry: './index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        }
    }

3. 配置 loader

这个不知道怎么配置,就先看这篇文章吧。

所以说,到这里我们的 webpack.config.js 就是这样的:

    var path = require('path');

    module.exports = {
        entry: './index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        module: {
            rules: [{
                test: /\.css$/,
                use: [
                    'style.loader',
                    'css-loader'
                ]
            }, {
                test: /\.(png|jpg|svg|git)$/,
                use: [
                    'file-loader'
                ]
            }, {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            }]
        }
    }

这里我们用到了三个 loader,需要先安装下

    npm install css-loader style-loader file-loader --save

这三个 loader 的作用大家还是自己去网上查找吧

4. 第一次打包

配置到这里,我们可以先来打包一下,有问题再改嘛!

命令行切换到项目目录下,执行以下命令:

    npm start


打包结束后,项目的目录结构

    project
        - css
        - dist
        - fonts
        - images
        - js
        - node-modules
        - favicon.ico
        - index.html
        - index.js
        - package.json
        - webpack.config.js

我们可以看到,项目根目录下面多出来一个 dist 的目录。没错,这个就是我们 webpack 打包后文件生成的目录,至于为什么会是 dist 目录,那是因为你在 webpack.config.js 的 output 中设置的 path。


现在我们来查看下 webpack 打包出了什么东西

        - dist
            - xxxx.jpg
            - xxxx.woff2
            - xxxx.jpg
            - xxxx.svg
            - bundle.js
            - xxxx.ttf
            - xxxx.eot
            - xxxx.woff

注:xxxx代表一串数字和字母的组合,为了表示方便就这么写了

打包文件中生成了一个 .js 文件,两个 .jpg 文件,四个字体文件(.woff2、.ttf、.eot、.woff),和一个 .svg 文件

我们来看下这是个类型的文件都来自哪里吧

1. bundle.js

bundle.js 是我们根据我们入口文件,将我们在入口文件中所有的依赖资源都打包进去生成的。这个也是我们最主要要关注的文件。

2. .jpg

两个 .jpg 文件是从哪里来的呢?查看了两张图片之后,其实我们会知道,这两个图片都是在我们自己写的 jb.css 中用来作为 background-image 引入的。我们说了,webpack 会根据入口文作为起点,并根据依赖关系图来进行打包的。换句话说,我们在入口文件中依赖了 jb.css,而 jb.css 依赖了两张 .jpg 图片,所以 webpack 根据依赖分析,将这两张图片也都一起打包进来了。

3. 字体文件 + svg

四个字体文件的来源就需要我们对 bootstrap 有一定了解了。如果熟悉 bootstrap 的同学肯定会知道,在 bootstrap 的依赖里面,他正是依赖了这些字体,也就是说这些字体文件是从 bootstrap.min.css 文件中被打包进来的。其实我在做这个项目的时候,把 bootstrap 的源码弄到本地的时候,会发现里面有一个 fonts 的文件夹,也就是我们项目根目录下的 fonts 文件夹,这里会有四个同样文件结尾的字体文件和一个 .svg 结尾的 svg 文件。而这五个文件不就是我们这里多出来的五个文件嘛

如果还不放心的话,我们可以再做个验证。修改 webpack.config.js 文件

    {
        test: /\.(png|jpg|svg|git)$/,
        use: [
        -    'file-loader'
        +    'file-loader?name=[hash:8].[name].[ext]'
        ]
    }, {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
        -    'file-loader'
        +    'file-loader?name=[hash:8].[name].[ext]'
        ]
    }

我们把图片和字体文件,在 file-loader 处理后让他的名字就变成 ’8位hash值-原文件名-后缀名‘的格式,那么我们就可以比对这几个文件的来源了。
重新打包后(你得先删除原来的 dist 文件夹),我们就可以发现我们的猜测是正确的!

4. 遗留问题

虽然我们的第一次打包成功了,但是还是留下了几个问题没有解决:第一,我的 js、css、字体、图片等资源都被打包进了 dist 目录,但是作为我们的项目最主要的 index.html 文件呢?没有这个文件,我们打包出来的东西还有什么意义呢!。第二,我的 images 文件夹里有那么多图片,你这个 webpack 打包后为什么就只有两张图片了,其他的呢?

那么接下来让我们急需解决。

5. html-webpack-plugin

要解决第一个问题,我们需要用到 html-webpack-plugin 插件。这个插件的具体说明可以查看这里。这个插件的作用是可以将 html 文件打包,并自动添加对打包后的 output 文件的引用。具体如何使用,请先安装:

    npm install html-webpack-plugin --save

修改配置文件:

    var path = require('path');
    +   var HtmlWebpackPlugin = require('html-webpack-plugin');

在 module 后面加

    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html'
        })
    ]

我们在配置文件中引入了一个插件,并向 HtmlWebpackPlugin 构造函数传递了一个对象参数,在这个对象参数中,我们指明了一个 template 字段,表面我们要打包的 html 的文件源。然后我们重新打包,再查看我们的目录就可以发现 dist 目录下多出了一个 index.html。由于这个项目本身就是一个简单的不依赖任何环境的项目,所以如果正常的话我们直接打开 index.html 页面就能在浏览器里正常显示了。虽然不知道会怎么样,但是我们还是打开来试试吧。

当我们打开 index.html 在浏览器中显示的时候,我们发现浏览器中好多图片都不见了。细想我们的项目代码,发现除了在 jb.css 中的背景图被正确显示以外,其他的定义在 img 标签中的图片没有一张是显示出来的。

所以虽然这个文件配置还有点问题没解决,那么我们先来解决 html 中的 img 问题吧,也就是我们上面提到的第二个问题。

6. html-withimg-loader

要解决第二个问题(也就是 html 中的 img 问题),我们需要用到 html-whithimg-loader,具体关于这个怎么用可以查看这里

修改配置文件,直接在 rules 中再添加一条配置规则

    {
        test: /\.(html|htm)$/,
        use: [
            'html-withimg-loader'
        ]
    }

这条配置规则表明,所有要处理的 html 文件首先会经过 html-withimg-loader 这个 loader 处理。就是这么简单能将我们第二个问题解决吗?试试看吧,事件是检验真理的唯一标准。

修改资源目录

重新打包,再查看 dist 目录,这一查看不要紧,发现 dist 目录下多了好多图片文件,密密麻麻,乱七八糟的,这些该不会就是我们 html 中的图片吧。按住自己的强迫症,先找到 index.html (我们最关心的还是他嘛),打开后再浏览器查看效果。发现果然,我们的图片都已经在了,而且样式也差不多对了。但是这么乱七八糟的 dist 目录,怎么会是我们这种强迫症患者所想要的结果呢!我们想要的是图片都放在图片文件夹下,字体都放在字体文件夹下,其他的比较少的就先让他在外面呆着吧。说干就干,我们来调整一下我们的配置文件

        {
            test: /\.(png|jpg|svg|git)$/,
            use: [
            -    'file-loader?name=[hash:8].[name].[ext]'
            +    'file-loader?name=images/[hash:8].[name].[ext]'
            ]
        }, 
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            use: [
            -    'file-loader?name=[hash:8].[name].[ext]'
            +    'file-loader?name=fonts/[hash:8].[name].[ext]'
            ]
        } 

自动删除 dist

还有一个问题我已经忍了很久了,每次打包前,我们都需要手动先去删除上次打包留下来的 dist 目录,这个就烦了,虽然只是一个 delete 的事情,但是做多了也烦啊!我们想能不能让 webpack 自动帮我们做了这件事,让我们不需要手动去删除。还好 webpack 够智能,总能满足你提出来的各种无理取闹。不过我们先要装一个插件:

    npm install clean-webpack-plugin --save

然后再配置文件中添加对这插件的引用

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

在 plugins 中添加对这个插件的使用

    new CleanWebpackPlugin(['dist'])

这样我们就能不用手动删除 dist 文件夹了。

重新打包下试试吧!

打包完后我们在查看 dist 目录就瞬间感觉清爽多了有木有!

    - dist
        - fonts
            - 一些字体文件
        - images
            - 一些图片文件

        - bundle.js
        - index.html

修改 index.html

将打包后的项目再次在浏览器中查看,并查看控制台会发现,控制台报了一堆错误。其中有几个是对一些 css、js 文件引用的错误。因为我们把需要用的 css、js 都打包进了 bundle.js 中了。而我们原来的项目是通过静态资源引用的方式一个个导入 html 文件中的。所以,当我们 webpack 打包成功后,就不需要对这些资源进行引用了,我们只需要对 bundle.js(我们打包后的文件)进行引用就可以了,所幸的是,打包后的文件 webpack 已经自动帮我们引用了。所以直接在原来项目中的 index.html 中干掉那些 css、js 就可以了。然后重新打包后就没有这些资源找不到的乱七八糟的错误了。

但是,有一个小问题就是关于我们的 .ico 文件。这是我们网站的图标文件。他的引用错误该如何解决呢?我们可以在生成 html 的时候,将这个问题先给解决了。修改配置文件

    new HtmlWebpackPlugin({
        template: './index.html',
    +    favicon: path.resolve(__dirname, './favicon.ico')
    })

这样的话我们的图标文件也就有了。

7. 关于 jquery

虽然一些乱七八糟的引用错误解决了,但是控制台留下了一个让我们非常头疼的问题:jquery 的引用问题:

    Uncaught Error: Bootstrap's JavaScript requires jQuery

这里我们需要用到 expose-loader 这个东西,关于他,可以查看这里,废话不多说:

    npm install expose-loader --save

在 module 的 rules 中再添加一个 loader

    {
        test: require.resolve('./js/jquery.min.js'), // 引入 jquery
        use: [{
            loader: 'expose-loader',
            options: '$'
        }, {
            loader: 'expose-loader',
            options: 'jQuery'
        }]
    }

当然,网上或许有其他方法关于引入 jquery 的,这里只是说一种。然后再打包我们的页面就没有问题了:各种资源都有了,js 写的效果也出现了。

8. 提取 css

虽然网站看上去没啥问题了,但是细心的同学肯定会发现:当我们打开网站的时候,他会先出现一个没有样式的页面,然后一闪而逝,最后才出现我们预期的样子。这是为什么呢?

原因很好理解,因为我们把 css 和 js 都打包进了同一个 bundle.js 里面了。但是,这个 bundle.js 是在页面最后面才加载进来的。也就是说,我们的样式被放在了页面的底部被加载。这完全不符合我们的预期啊。我们希望的是样式在 head 中加载,而 js 脚本才放在页面底部加载。所以我们就不能把 css 和 js 一起打包进 bundle.js 中了。

extract-text-webpack-plugin

详细资料看这里

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

增加 require

    var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');

修改 css rules

    {
        test: /\.css$/,
        use: ExtractTextWebpackPlugin.extract[{
            fallback: 'style-loader',
            use: 'css-loader'
        }]
    }

增加 plugin

    new ExtractTextWebpackPlugin('style.css')

打包,然后我们会发现 dist 中多了一个 style.css,然后再 index.html 的 head 中的多了对这个 css 的引用

9. 其他

1. 压缩 js

增加 plugin

    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    })

2. 压缩 html

new HtmlWebpackPlugin({
    template: './index.html',
    favicon: path.resolve(__dirname, './favicon.ico'),
    minify: {
        removeAttributeQuotes: true,
        removeComments: true,
        removeEmptyAttribute: true,
        collapseWhitespace: true
    }
}),

3. 优化图片

    {
        test: /\.(jpg|png|gif|svg)$/,
        - use: 'file-loader?name=images/[hash:8].[name].[ext]'
        + use: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]'
    }

总结

暂时就先那么多吧,没时间写了,以后再说。第一次发文,求轻虐。

参考文档

webpack(v3.5.5)中文文档