使用webpack替换grunt

2021年09月26日Web前端0

由于一直维护的项目打包是基于grunt的,虽然自己已经实现了grunt下代理和es6等功能,但是为了让老项目更现代化些,预将webpack替换掉grunt。

配置webpack

entry

首先第一步先配置基础的webpack配置,grunt的入口是html,而代码中并没有使用import/export,所以我们需要手动将所有js添加到entry中。按js作用和类型我分成了vendor,controllers、services等。毕竟分包减小单个js文件体积以提升效率。

为了避免以后每次增加一个js就要改entry,使用了glob做文件遍历。如:

{
  // ...
  entry: {
    other: [/** 配置的js */],
    app: [
      ...glob.sync('./app/scripts/filters/**/*.js'),
      ...glob.sync('./app/scripts/directives/**/*.js'),
      // ...
    ],
    controller: [
      ...glob.sync('./app/scripts/controllers/**/*.js'),
    ]
    // ...
  }
  // ...
}

rules

下面就要配置loader了,js、css、less、scss这些都是用正常的通用配置。

其中需要注意的是处理html(index.html, main.html,以及views下的各个按需加载的页面)的时,一开始使用的是html-loader,由于它无法处理img上的srcset属性,所以使用了html-srcsets-loader

<img src="/images/qiye_logo.png" class="f-margin-top-5" srcset="/images/qiye_logo@2x.png 2x" alt="网易合作伙伴平台" />
{
  test: /\.html$/i,
  use: [{
    loader: 'html-srcsets-loader',
    options: {
      attrs: ['img:src', ':srcset'],
      minimize: true,
      removeComments: true
    }
  }]
}

plugins

我们需要使用html-webpack-plugin插件来处理所有的html文件。包括主页面和各个按需引入的页面。同样的,为了提升使用体验我们可以用glob曲循环变量html页面的方式配置。

const files = glob.sync("./app/views/**/*.html");

files.forEach((pa) => {
  let opt = Object.assign({}, defaults);
  // ... 处理
  arr.push(new HtmlWebpackPlugin(opt));
});

由于项目中使用了jQuery,做一个全局的变量:

new webpack.ProvidePlugin({
  $: 'jquery',
  jQuery: 'jquery',
})

需要注意,我们需在angularjs之前先绑定jQuery。可在angularjs源码的bindJQuery方法中看到, 不然angular.element就是angularjs内置的jQLite了(jQuery子集,有jq的基本功能)

// bind to jQuery if present;
jQuery = window.jQuery;
// Use jQuery if it exists with proper functionality, otherwise default to us.

由于项目按需加载views下的页面,get请求会被浏览器缓存,所以需要加一个query参数防止,使用了DefinePlugin做变量替换。

new webpack.DefinePlugin({
  __auto_html_version: (new Date()).valueOf() + '',
}),

多页面配置

由于项目是多页面的,所以需要借助html-webpack-plugin配置多页面。

new HtmlWebpackPlugin({
  template: path.join(__dirname, '../app/index.html'),
  minify: true,
  inject: true,
  filename: 'index.html',
  chunks:['other', 'access']
}),
new HtmlWebpackPlugin({
  template: path.join(__dirname, '../app/main.html'),
  minify: true,
  inject: true,
  filename: 'main.html',
  chunks:['other', 'app', 'services']
}),

去除html处理

使用一段时间发现,热更新和打包都比较有点慢。而views目录下的html仅有部分需要处理图片,所以将图片使用import方式引入,挂载到$scope下。 整个views文件夹用copy-webpack-plugin拷贝,节省差不多30s左右的打包时间 。当然这会导致用户加载html时间略微变长。

其他修改

dev

webpack的特殊配置基本OK了,其他都是一些常规的,如dev下配置一些webpack-dev-server的配置,mini-css-extract-plugin插件等等。

可以使用hard-source-webpack-plugin插件加快热更新和以后的打包速度,热更新可以从十几秒优化到1s内。

prod

build情况下,可能还需要加些optimization的配置,对node_modules下的第三方依赖做个拆分。

第三方依赖

将所有第三方插件用npm的方式安装,并且在主js文件(第一个执行的js文件)以import的方式引入。注意对应的版本号,尽量使用相同的版本号,后期等稳定后再升级。

第三方依赖的样式文件也同样需要引入。项目是机遇Angularjs的,大致如下:

import angular from 'angular';
import ngAnimate from 'angular-animate';
import ngAria from 'angular-aria';
import ngCookies from 'angular-cookies';
import ngMessages from 'angular-messages';
import ngResource from 'angular-resource';
import ngRoute from 'angular-route';
import ngSanitize from 'angular-sanitize';
import angularLoadingBar from 'angular-loading-bar';
import uibs from 'angular-ui-bootstrap';
import toastr from 'angular-toastr';
import uiSelect from 'ui-select';
import JSZip from 'jszip';
import Clipboard from 'clipboard';
import _ from 'lodash';
// import ngEcharts from 'angular-echarts';
import 'angular-translate';
import 'angular-translate-loader-static-files';
import moment from 'moment';
import * as echarts from 'echarts';

有些如翻译的只要直接impot就行了。接下来是替换angular的module初始化中的配置,原先是字符串,现在需要使用对象。大致如下:

var app =
  angular
  .module('adminApp', [
    ngAnimate,
    ngAria,
    ngCookies,
    ngMessages,
    ngResource,
    ngRoute,
    ngSanitize,
    'pascalprecht.translate',
    uibs,
    uiSelect,
    // 'ui.bootstrap',
    toastr,
    angularLoadingBar,
    // 'TreeView',
    'angular-echarts',
    'ng.shims.placeholder'
  ])

部分依赖需要绑定到window下,如echarts,mmoment这种需要import来,挂到window下。因为老项目中,都是直接使用的这些变量。

window.moment = moment;
window.echarts = echarts;
window.Clipboard = Clipboard;
window.JSZip = JSZip;
window._ = _;

最后,angular.module中返回值也需要绑定到window下,其他老模块会使用到。

window.app = app; // 老文件用

这样就可以了。