阅读 534

Webpack 4 构建大型项目实践 / 处理图片、样式和字体

本文所用示例的仓库地址: gayhub

上文使用 HtmlWebpackPlugin 生成了一个 index.html 文件,并且插件自动把打包后的资源添加到 index.html 文件中,使我们可以打开 index.html 在浏览器看到 js 的执行效果。本节我们将用 Webpack Loaders 来处理工作中会用到的其他三种类型文件:图片、样式、字体。

从这一节开始,由于项目资源类型变得复杂,每一节的测试项目会放到 demo/ 下,不再是文档根目录。

Loaders

官网对 Loaders 的介绍很简单,只有简单三点:

  • 可以用 Loaders 打包任何 Javascript 之外的任何静态资源
  • 用 Node.js 编写一个 Loader 很简单
  • 激活 Loader 有两种方式:
    • 在引入语句中添加 loaderName! , 比如 import 'style-loader!css-loader?modules!./css/test.css'
    • rules 中配置,见下文

处理图片

file-loader

yarn add file-loader
复制代码

通常情况下,我们只需要把图片复制到(打包)目标目录,此时我们使用 file-laoder

{
  test: /\.(png|jpe?g|gif|webp)$/,
  use: [
    {
      loader: 'file-loader',
      options: {
        // 文件命名
        name: '[name].[ext]',
        // 输出路径
        outputPath: 'imgs/'
      }
    }
  ]
}
复制代码

执行 webpack 命令后,我们可以看到图片已经被打包到了 dist/imgs/ 目录下,并且命名未修改。

url-loader

yarn add url-loader
复制代码

当我们的图片文件存在一些小标签时,如果每个小标签都独立请求,显然是会造成带宽浪费的,这不合规矩。这个时候我们需要有类似雪碧图一样的方案,把多个小图片整合为一个请求。url-loader 就可以做这样的整合,但和雪碧图不同的是,它是把大小小于限定值的图片转成 base64 编码放到打包后的代码中。

{
  test: /\.(png|jpe?g|gif|webp)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        // 文件命名
        name: '[name].[ext]',
        // 输出路径
        outputPath: 'imgs/',
        // 小于 10k 的图片转成 base64 编码
        limit: 10240
      }
    }
  ]
}
复制代码

执行 webpack 命令后,我们可以看到只有 多啦A梦.jpg 图片被打包到了 dist/imgs/ 目录下,其他 5 个小图被打包成了一串 base64 码(在 dist/dist.js 中)。

  • 大于 10k 的图

    /***/ "./src/imgs/多啦A梦.jpg":
    /*!***************************!*\
      !*** ./src/imgs/多啦A梦.jpg ***!
      \***************************/
    /*! no static exports found */
    /***/ (function(module, exports, __webpack_require__) {
    
    eval("module.exports = __webpack_require__.p + \"imgs/多啦A梦.jpg\";\n\n//# sourceURL=webpack:///./src/imgs/%E5%A4%9A%E5%95%A6A%E6%A2%A6.jpg?");
    
    /***/ }),
    复制代码
  • 小于 10k 的图

    /***/ "./src/imgs/分享.png":
    /*!*************************!*\
      !*** ./src/imgs/分享.png ***!
      \*************************/
    /*! no static exports found */
    /***/ (function(module, exports) {
    
    eval("module.exports = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAR7ElEQVR4Xu2dC9CuUxXHf0TkfolQrinkLspRKEVuRVO55Ci3kqQkoiJ3iVDINYVJLplyHddBkqjEYEopFBWSJGLk0vzNPjnzfef73vfd79577f0+a82YY+Y8e621//v5n3fvZ6/LTLg4Ao7AhAjM5Ng4Ao7AxAg4QfztcAQmQcAJ4q+HI+AE8XfAEYhDwH9B4nDzUR1BwAnSkYX2acYh4ASJw81HdQQBJ0hHFtqnGYeAEyQONx/VEQScIB1ZaJ9mHAJOkDjcfFRHEHCCdGShfZpxCDhB4nDzUR1BwAnSkYX2acYh4ASJw81HdQQBJ0hHFtqnGYeAEyQONx/VEQScIB1Z6ITTXBGYAqwBrAusNAPdjwG3AjeHP/X/TyX0oZgqJ0gxqJs2tBqwI/ARYNHImVwJnAWcFzneZJgTxAT2ZoyuBRwCbJzQ44eAI4CTE+rMpsoJkg3aphXPCRwF7AbkekfuArYD9Ge1kmvy1U7YHeuJwHrA2cCSPZ8c/oHngcPCfy8Mry69BidIekxb1nhA2FKVnoMO85sBT5Q23MueE6QXQt35+1OAXQ2new/wHuCvhj6MM+0EqWk17Hw5EtjXzvz/Lf8W0IeBpyvw5WUXnCC1rISdHzsA37MzP86yPgdvUos/TpBaVsLGD1323QLMamN+QqsHAQfX4JMTpIZVsPPhfmApO/OTWl473MKbuucEMYXf1Lj+lT7Q1IPJjd8RwllesvTRCWKJvp3txYA/ArPbudCX5anAOX09mekhJ0gmYCtXezywR+U+yr17geWBF618dYJYIW9ndz7gn3bmB7a8BXDJwKMSDXCCJAKyITW7AKc35O8FwNZW/jpBrJC3s3sN8F478wNbfhZYAHhm4JEJBjhBEoDYkIrZQuLSLA35LFc3BK618NkJYoG6nU3FOpm8aENO+XBg/yF1RA13gkTB1uygfUKeR2sTuAx4v4XTThAL1O1sfjekztp5EGf5D8Cb4oYON8oJMhx+rY2+FNi8NaeB5wCdn4qLE6Q45KYGLwc2NfUg3vj8FglVTpD4BWtxpPbyytxrUZYN4TFFfXeCFIXb3Jgu3VS6p0VZAniwtONOkNKI29o7DfiErQvR1hUi86/o0ZEDnSCRwDU6THcJhzboux/SG1y0Fl3eCji/QcdvA9a08Nt/QSxQt7OpsqFVVQ3pE4rvWG0NnSB9rtAIPabKIcqxaEm2tarp6wRp6TVJ46vq4n4pjapiWhYCVDG+uDhBikNuavB9IRdkcVMvBjN+MbDlYEPSPe0ESYdlzZoWAY4DtqnZyQl8U2X5q6z8doJYIV/G7szAZ0Jx6LnLmExq5XfACoBZZRMnSNL1rEqZisKdAaj5TauiS019wTITJ4gZ9NkMzxUa1OwO6BekVdHdh+r0mv16CDgnSKuvz4z91udQnTVeNwLTMglOHIubE2QE3iRAX6XOBDYYjem8XLPrxBrm4gSpYRWG80F3GrrbGBUxS6+dEYBOkHZfq3UApdAu1+4UxnmukkTKV/lvLXNygtSyEv37oRpRRwM79T+k7ydVcVGZexbyg9DU08L2hDadIFUtR09ndg5VSUSSlPLTEAz4FHCRQeTs14H9Uk4olS4nSCok8+p5Y+g8q21VSnkcUCkgbdWmiZrp6IXds8BXTuV56JfQtIL7ZIA6QVK+bnl0qU3yVzKoFinUl3CiIEB9EVMV+BUz2JZKtZpW8pZK+lQrTpBql4aNAHWeXTqxi/cB2qrd0Kfe7UNr6FSdqPQ5Wk1DFUZSvThB6lsihXafkKmi+TC9/7YL26GYuxYVoFbIyDeAP9UH+cQeOUHqWq3dwv4/dWDh9eEQrq5Sw4q+cq0fDvL6dVOW4tiQln8DDwH6tboV+AUgkjQnTpA6lmyV0IpZAYYpReeLL4T9fkq9ndHlBLFd6jlCKPpngVcldEUBfup9LnI8kVBv51Q5QeyW/IPhK9EbErugw68+nd6cWG8n1TlByi+7AgvVAk3prylFe3x9EtYdxvMpFXdZlxOk3OprC7VX6E0+Z2KzNwIfBx5IrLfz6pwgZV6Bt4UzwVsSm3skkE5xTC4ZEHCCZAB1OpXzhMBCpY6mxFqHcG3TFCbyZN4pdFt7ykXrNpLjZz8VOAZYODEwOoR/LNwtJFbt6sYi4ARJ/04osFBxTuslVv2fEPIh0vkhPDG4E6lzgqQD+tXAl0PYdup2YdcBO1j0x0gHT5uanCBp1k2/FvrV0K9HSvlbCDtX4xsXAwRGgSCKW1KexOrA7GMwVEzQLzPGAimw8FhA543UclIIR1cSk4sRAi0SRCTYAlBJyikD5GT/GrgJUKfXaxPgvWv4QpU6sFDV17WdUoCfizECLRFEPeqUOKSw62Ev2rR1+XYI9dCvzCCiuwyFboucKeUZQB2g9IvkUgkCLRBE/0IrwebTGTBTkQKR7uQ+dCuw8JAQANjH4wM9ovbMnwoh4gMN9IfzIlA7QRSvpAw0VSfPKQrV0N3CRMk8mwNqgKnch5Sim3AVl74wpVLXlQ6BmgmiQ6oSiEqJtlo6V5w7ncHXh62YzjypRVs8FX0bdIuX2g/XNwkCNRJkXuCKDHv8fl+Ew8NZ4PNhS6Vi0CnlTkB53vrTpXIEaiOIYpf0pWllY9wezRAi8nS4SFSlEJdGEKiJIAoH141x6hCNGpbix+Ejw8M1OOM+9I9ATQRRJQ8dWEdJ1HJZ2X1mLcRGCUyLudRCkK2t2vxmBF0lbg4EFGTo0igCNRBkPuB+QH+OgujGXofw34zCZLo+hxoIMipbKyUufRE4tesv1SjN35ogCh9RMbNZGgf1h4BK9/ghvPGFHOu+NUFUe1aXc62Kbt6VTqvGLy4jiIAlQRSVq1iosSHqLcCsjD5l9qnWbZMlNVsAuQYfLQny0Zr7QkyyOApD39EP4TW8vvl9sCTI+cBW+aeY1ILip0btriYpQKOmzJIg2l619mlXX9x0GHfpCAJWBFkeUOZca6L0XRWBc+kIAlYEUeHmHzWKsRVmjcLVtttWi60LNRVZblFUuUSNYVw6gIAVQQ4IuRYtQvwOby3Q4rLF+WxFEAXx6Q6hRflQw9vDFvE29dmKIEo1PcJ05vHGtx3ByON4NEZ8pBVB9Kn0W41iq0ISVzfqu7s9IAJWBFEFkbMG9LWWx98KKKTdpQMIWBFEB13lnrcoqo+lIm8uHUDAiiALAP9oEN/fD1DqtMHpuctjEbAiiPxQI5g3N7Yk52QqVN0YDN1x15IgKvepcpstieoCez/AllZsSF8tCbJhY1+DXgC0NfSegEO+dC0NtySIcFKxhqUaAeyS0HahEXfdzRQIWBNk31C5PcVccuvYALg+txHXXxcC1gRRPoiKq72mLljGeaPc81Z+6SqHsi33rAkitFQsWs0va5c7gL38V6T2ZUrrXw0EUXfY2wF1bmpB1OxGRNGdiMuII1ADQQTxqiF8Y+ZG8NYXLXW1VXeqvzfis7sZgUAtBGlpqzU9zGppcDRwlIefRLx9DQypiSBqf3Ae8OEGcBvropqCfjX8qrzYoP/u8gQI1ESQaS6KJKr23qLcA+wDXNai8+7zeARqJIh8UhemlutP/ST4f7e/dG0jUCNBpiH6uVDeU1uvFuUl4PvAfuGup8U5dN7nmgmixdkotBPIfUl3NqBWz4q1yiFfA/Sfd7TNgW5GnbUTZNrUPxkOwWrLnFL0qfZg4M/A3KEts/Llc8jjwZY38cyBbiadrRBk2vTVakAv8NJD4KF7i9PCL9ODM9CzeCgoMXUIG5MNVU0tbbvUU8SlcgRaI8g0OFV6R9UZpwDL9MBYPQJVkf1nIc33yj7XRLnnx2bsuquUY/Vi/1Wf/vhjBgi0SpDpoVKgozpVLQZoC7ZoaJypjEV1r1JI/TAiIqohZy8ixtrQL4kqTT4Qq8DH5UNgFAiSD51XNKtF3G6ha+2CGQw+B5wUqk2q6r1LJQg4QQZbiHmA/UMLhNkGG9rX00+E849qhok0LsYIOEHiFkAHecVg5brxV/6JUgDOBXSf4mKEgBNkOODXBE4E3j6cmglHKwdFEQX6wOBigIATJA3o+qqmdg5qjZBDLgX29hyUHNBOrtMJkg7zWYHdAbV2yHEjrxyU08OFqeegpFu3STU5QdIDrYO82jtoa6RsydTyVMg/0adnL4GaGt0x+pwg+QBW/JgO8rnyW1TsQr9WZwKeg5JpHZ0gmYCdTq0O8qoiqT9zyF0hB+WqHMq7rtMJUu4NUE/4I4eMI5vMW89BybCWTpAMoE6iUmeSPcJlY44e8dpqKXRfxSS0BXMZEgEnyJAARg6fP5wfFKyYS3LloCwJrA2sFYJF1wBmHzOJZ4Hbwv2NessrMPPhXBPNqdcJkhPd3rp1b6KDvAIic4g+B+sgf2oC5brrUalYESNG1JVL9QbUWezRGAUWY5wgFqiPt7lO6NmY6yCvIncqJqEC3IPKTiEHZ9lBB07yvKIPlKj2WEKdWVQ5QbLAGqVUa7FNSM3VNiaH3BruZ3rloCh6WX0kVcooly8ih+oOVN1vxQmS4zUcTqcO8nuGYMV5h1M1w9EKfrwgZDXOKAdl4VC2KHYrNajLCqPZsdaWfE6QQZez3POvDf+C66tXLvnmmByUlQBlXKbO/e/l/yPAZuFg3+vZon/vBCkKd5Qx9XFUIOSWUaN7D1KC1mGAtl8qeJfj83NvL0AhNFsA1/XzcKlnnCClkB7ejlpn63C72vCqqtWgWsf6YHFnLR46QWpZif790H5dPVWUez+K8hdABTO07TIXJ4j5EkQ5oIs5XTKqBJLqeY2a/Bx4Zw1BmE6Qtl+thcIhu7V22v2grpRjRQOYihPEFP5kxnWQVw0vfQkaFVE9M11OqrWEmThBzKDPYvjdwAnAilm0l1eq0rA7lzf7ikUniCX6eWxrTXULroN86fuM1DN6PvyKqMqLiThBTGAvYlQVJ3WQV+j7HEUs5jGiGmGKLDARJ4gJ7EWN6iCvi0BtVVrstaLPvfqkbVIfzAlS9F01NbZCCK1v8SD/LkAZk8XFCVIccnODqtaoqOGW5JBQKaa4z06Q4pCbG1QYx8rmXgzmwI3A+oMNSfO0EyQNjq1o0a37k604O52f8jlH6H9PKJwgPSEaqQfWBfSvcYuyiEV8lhOkxVcl3mcVsWu19ZsKhKtTWFFxghSF29yYmgCpUU+Loi7El5d23AlSGnFbe4r+PcLWhWjr24e+89EKYgY6QWJQa3fMXsAxjbqvypTFt4dOkEbflki3dwVOiRxrPWyTkC9f1A8nSFG4zY19ALjY3Is4B1YBVKi7qDhBisJtbmw54B5zL+IcUNPU4o1NnSBxi9XyKFUPmbOxCdxtdfvvBGnsTUng7hXAxgn0lFRxfKjCWNLmy7acIMUhNzeoHBGl57YkmwIidnFxghSH3NygKjYqx2Jmc0/6c0BtE5QZadJmzgnS3yKN2lMXAmpn0IIcFCrBm/jqBDGB3dzoelYJSAPOXF+tFrfsJ+IEGXDFRuhxdYBSd6ia5QxgF0sHnSCW6NvaVo1f9QmpNU9dRbXVgUt/mokTxAz6KgyrNJAqGNYoqmZvfuvvBKnx1Sjnk5r13AGooENNcg4wtQaHnCA1rIKtD6uGbrS13K7fC6hXYxWpwU4Q25ezFuvK1rsWmMvYoftCf5AqWh8ICyeI8RtRkfnVgWuABY180lZvI0Ctq6sRJ0g1S1GFI6oSf3XGzrYTTVJ9EXVxqYruVYkTpKrlqMKZ+UOvQrVCKyHHAXtbhZL0mqATpBdC3fx73Y0oB/xAYKlMENwE7APckkl/ErVOkCQwjqySWYAdQqu3ZRLNUl1sDwVuSKQvqxonSFZ4R0a5flG2BvYFlPo6qCgS96JQZf72QQdbPu8EsUS/TdtqizYFWAlYAlgshKOrRYEa3jwEPAioW60a3yjm62brkJFYqJ0gscj5uE4g4ATpxDL7JGMRcILEIufjOoGAE6QTy+yTjEXACRKLnI/rBAJOkE4ss08yFgEnSCxyPq4TCDhBOrHMPslYBJwgscj5uE4g4ATpxDL7JGMRcILEIufjOoGAE6QTy+yTjEXACRKLnI/rBAJOkE4ss08yFgEnSCxyPq4TCDhBOrHMPslYBJwgscj5uE4g4ATpxDL7JGMR+B/NGhvn0Vq4vQAAAABJRU5ErkJggg==\"\n\n//# sourceURL=webpack:///./src/imgs/%E5%88%86%E4%BA%AB.png?");
    /***/ })
    复制代码

处理样式

如果我们不使用 loader 来处理样式,执行 webpack 可以看到这样的提示 You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.

CSS

yarn add css-loader style-loader
复制代码
  • css-loader 解析 CSS 文件
  • style-loader 把样式插入到 DOM 中

loader 从右往左、从下往上执行

{
    test: /\.css$/,
    use: [
        'style-loader',
        'css-loader'
    ]
}
复制代码

此时执行 webpack 就能正确打包了,但并不会再 dist/ 下生成 .css 文件,而是把样式以 style 标签的形式插入到 index.htmlhead 标签中(在浏览器控制台查看,插入前样式文件在 JS 中)。

CSS 预处理器

由于我是看黄轶讲师的“高仿饿了么”课程后,才开始在项目中使用预处理器,所以会更喜欢 stylus ,本文的例子也使用 stylus 。

stylus-loader 默认项目中存在 stylus 依赖,所以安装 stylus-loader 时不要忘记安装 stylus

yarn add stylus stylus-loader css-loader style-loader
复制代码
{
    test: /\.styl(us)?$/,
    use: [
        'style-loader',
        {
            loader: 'css-loader',
            options: {
                importLoaders: 1 // 在 css-loader 前执行的 loader 数量
            }
        },
        {
            loader: 'stylus-loader',
            options: {
                preferPathResolver: 'webpack' // 优先使用 webpack 用于路径解析,找不到再使用 stylus-loader 的路径解析
            }
        }
    ]
}
复制代码

PostCSS

几年以前写 CSS 有个蛋疼的地方,某些属性我们需要为不同浏览器加上不同的前缀,比如 Firefox 的 -moz-transform 和 IE 的 -ms-transform ,但有了 PostCSS 后你只需要写无前缀的属性,PostCSS 会根据 Can i use 的数据为你的 CSS 属性补充上前缀。

yarn add postcss-loader -D
复制代码
{
    test: /\.styl(us)?$/,
    use: [
        'style-loader',
        {
            loader: 'css-loader',
            options: {
                importLoaders: 2 // 在 css-loader 前执行的 loader 数量
            }
        },
        'postcss-loader',
        {
            loader: 'stylus-loader',
            options: {
                preferPathResolver: 'webpack' // 优先使用 webpack 用于路径解析,找不到再使用 stylus-loader 的路径解析
            }
        }
    ]
}
复制代码

PostCSS 配置文件 postcss.config.js ,配置自动补全(需要 autoprefixer 插件)

yarn add autoprefixer -D
复制代码

postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}
复制代码

此时执行 webpack 命令,可以在浏览器中看到 transform rotate(180deg) 被翻译成 -webkit-transform: rotate(180deg); transform: rotate(180deg);

两个注意点:

  • postcss-loader 的顺序,在 css-loader 前一步执行
  • 可以在 Webpack 配置 JS 中配置 postcss-loader 的地方使用 options 来配置 PostCSS ,但更推荐在项目根目录添加 postcss.config.js 配置文件,别人能更容易知道你使用了 PostCSS。

从 JS 分离出样式文件

上文有提到 style-loader 把样式插入到 html 文件,这样做减少了请求数。但正常项目样式文件会占据不小的体积,要知道在插入 index.html 前我们的样式文件是储存在 JS 文件中的,所以 JS 文件会非常大,并且我们可能需要更清晰的生成物结构,所以也应当清楚如何把样式从 js 中分离出来 —— 使用 MiniCssExtractPlugin

yarn add mini-css-extract-plugin -D
复制代码

style-loader 替换为 MiniCssExtractPlugin Loader

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
        {
            test: /\.styl(us)?$/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        publicPath: '../',
                        hmr: process.env.NODE_ENV === 'development',
                        reloadAll: true
                    },
                },
                {
                    loader: 'css-loader',
                    options: {
                        importLoaders: 2 // 在 css-loader 前执行的 loader 数量
                    }
                },
                'postcss-loader',
                {
                    loader: 'stylus-loader',
                    options: {
                        preferPathResolver: 'webpack' // 优先使用 webpack 用于路径解析,找不到再使用 stylus-loader 的路径解析
                    }
                }
            ]
        }
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
      chunkFilename: 'css/[name].css',
    }),
  ]
};
复制代码

执行 webpack 命令后可以看到,在 dist/css 目录下生成了 main.css 文件,并且该文件在 index.html 中引入了。

处理字体文件

字体文件和图片类似,只需要拷贝并放到 dist/fonts/ 目录下,仍然使用 file-loader / url-loader

{
    test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i,
    use: {
        loader: 'url-loader',
        options: {
            limit: 4096,
            name: '[name]_[hash:5].[ext]',
            outputPath: 'fonts/'
        }
    }
}
复制代码

另外提一句,现在更流行使用 svg 来绘制图标,绘制路径存在 iconfong.js 中(阿里图标库下载举例),此时切记不要把 iconfong.js 放在 src/ 目录,因为打包 iconfont.js 毫无意义,只是增加打包时长罢了。应当放到不用打包的静态目录,比如 vue-cli 2 的 static/ 和 vue-cli 3 的 public/ ,并在 html 中引用他们,打包时使用 CopyWebpackPluginstatic / public 整个文件夹复制到 dist/

参考文档

关注下面的标签,发现更多相似文章
评论