webpack4下通用前端配置

534 阅读5分钟

前言

Web前端应用开发主要术语有:HTML、CSS、DOM、JavaScript,前三者的标准由W3C制定,后者由ECMA制定,可以认为DOM规范提供了前端页面拥抱JS技术的接口。一般Web应用运行在浏览器中,由浏览器内核提供的渲染引擎、JS执行引擎共同完成程序的加载。不同的浏览器对HTML、CSS、JS规范的支持有所差异,所以前端的这三部分都存在兼容性问题。另外,浏览器厂商实现JS操DOM的特定API被称作BOM,这一部分在不同浏览器中更是存在很多差异。

自前后端分离后,前端技术发展的热火朝天,越来越多的技术名词被创造并实践:MVVM、Hybrid、WebAssembly、PWA、V8、NodeJS、ReactNative、QuickJS...移动端对此乐此不疲

前端技术大爆炸时代,相信很多开发朋友都想寻求一种最合理有效的应用构建方式,目前看webpack构建工具最为流行。下面就说说怎么从新开始撸一套基于webpack4的健壮的项目构建方案。

基于webpack的项目构建方案

需求

  • 基础功能:图片、字体、样式处理
  • 扩展支持:less、sass、ts、js新特性
  • 按要求格式输出
- dist/[模块名]
    - assets
        - font
        - image
    - css
    - js
    - index.html
  • 差异化构建:多环境构建、单页多页应用混合构建
  • 适配要求:移动端自适应,浏览器兼容性

需求分析

下面按需求逐条分析该如何实现

基础功能:图片、字体、样式处理

给出webpack/module/rules配置

 {
      test: /\.(jpg|png|svg|gif)$/,
      use: [
          {
              loader: 'url-loader',
              options: {
                  esModule: false,
                  name: '[name].[hash].[ext]',
                  outputPath: 'assets/image/',
                  limit: 2048
              }
          }
      ]
  },
  {
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      use: [
          {
              loader: 'url-loader',
              options: {
                  esModule: false,
                  name: '[name].[hash].[ext]',
                  outputPath: 'assets/font/',
                  limit: 2048
              }
          }
      ]
  },
   {
      test: /\.css$/,
      use: [
          miniCssConfig,
          'css-loader',
          postCssConfig
      ]
  },
  {
      test: /\.less$/,
      use: [
          miniCssConfig,
          'css-loader',
          postCssConfig,
          'less-loader'
      ]
  },
  {
      test: /\.scss$/,
      use: [
          miniCssConfig,
          'css-loader',
          postCssConfig,
          'sass-loader'
      ]
  },

这里解释下处理图片、字体为何使用url-loader而不是file-loader(简而言之就是前者更好)

如果页面图片较多,发很多 http 请求,会降低页面性能。这个问题可以通过 url-loader 解决。url-loader 会将引入的图片以 base64 编码并打包到文件中,最终只需要引入这个dataURL 就能访问图片了。当然,如果图片较大,编码会消耗性能。因此 url-loader 提供了一个 limit 参数,小于 limit 字节的文件会被转为 base64,大于 limit 的会使用 file-loader 的参数进行命名,并把图片 copy 到指定文件夹内。

less-loader用于将less样式文件转为css文件,使用前需引入less库,sass-loader同理。

这部分依赖的库整理如下:

  • url-loader
  • less
  • less-loader
  • node-sass
  • sass-loader

扩展支持:less、sass、ts

显然前面一节已经提到了如何支持less、sass,这里只说如何支持ts开发项目。有两种方式可实现ts开发:ts-loader方式和@babel/preset-typescript方式——可以理解为webpack方案和babel方案,我这里选择后者,所以得先谈谈babel的配置。

Babel is a JavaScript compiler. Use next generation JavaScript, today.

引自babel官网首页,第二句话是说babel可以让我们使用下一代js语法。JS被称作动态脚本语言,动态是指在编译执行时才确定变量类型。要想提前享福,得啃硬骨头——babel会接收当前代码得到AST重新生成代码,说babel是js编译器也不为过。

给出babel配置如下

module.exports = function (api) {
    api.cache(true)

    const presets = [
        [
            "@babel/preset-env",
            {
                modules: false,
                useBuiltIns: "entry",
                corejs: {
                    version: 3,
                    proposals: true
                }
            }
        ],
        [
            "@babel/preset-typescript"
        ]
    ]

    const plugins = [
        ["transform-es2015-modules-commonjs"],
        ["@babel/plugin-proposal-class-properties", { "loose": true }],
        'dynamic-import-node'
    ]

    return {
        presets,
        plugins
    }
}

babel的配置一般由preset(预设)和plugin(插件/扩展)两部分构成,前者决定项目中JS的起点(躯干),后者决定项目JS的丰度(四肢),可以说两者相辅相成。

添加预设@babel/preset-env可以快速实现下一代js语法支持,它本身也支持一些个性化配置:比如使用core-js3实现新的API,结合.browserslistrc设定浏览器支持范围,useBuiltIns决定了babel使用js垫片的方式。

同样地,添加预设@babel/preset-typescript便可一键支持ts开发,毋庸置疑,这个预设底层肯定也是转换ts为js了的。

对于js新特性,包括新语法和新API两部分,新语法由babel搞定,core-js实现后者。

在实际编码过程中可能会遇到一些语法上的问题:

  • ESModule 和 commonjs module 混用 详情
  • babel编译class 及其 属性插件 详情
  • import()支持 详情
  • babel-plugin-import 为何物 详情

按要求格式输出

再来回顾下要求的格式

- dist/[模块名]
    - assets
        - font
        - image
    - css
    - js
    - index.html

也就是说图片、字体、样式、js文件存放在不同目录。

图片、字体输出配置

回顾基础功能:图片、字体、样式处理,可以发现rules中图片和文字输出目录的相关配置

 outputPath: 'assets/image/', 
 outputPath: 'assets/font/',

js文件的输出配置

 output: {
      filename: isPrd ? 'js/[name]-[chunkhash].js' : 'js/[name]-[hash].js',
      path: pathSolve(`dist/${targetEntryName}`)
  },

css文件输出配置

css呢?别急。实践过的人会发现webpack默认是将css包含在js文件中的,所以需要使用插件mini-css-extract-plugin将其提取到单独的文件中。本地开发模式下为了加快构建速度,可以不用考虑这一点。

mini-css-extract-plugin的使用

插件mini-css-extract-plugin的配置分两部分:loader和plugins。在发布模式下,将会使用MiniCssExtractPlugin.loader替换style-loader处理样式,因此loader部分的配置类似如下:

loader: MiniCssExtractPlugin.loader,
options: {
    publicPath: '../',
    reloadAll: true
}

plugins中配置如下:

new MiniCssExtractPlugin({
    filename: 'css/[name].[contenthash:8].css',
    chunkFilename: 'css/[id].[contenthash:8].css',
    ignoreOrder: true
})

所以plugin中指定了提取出的css的目录和文件名,当然也可以通过loader配置中的publicPath也可以指明目录,比如../css

使用这个插件遇到的一些问题这里罗列一下:

  • css文件中的url路径问题 参考这里
  • MiniCssExtractPlugin使用时css文件中的url路径问题 参考这里
  • MiniCssExtractPlugin 会抛出相同样式在不同文件中引入顺序问题 参考这里
css压缩优化

使用OptimizeCSSAssetsPlugin压缩css文件,它的配置如下

 new OptimizeCSSAssetsPlugin({
    cssProcessor: require('cssnano'),
    cssProcessorPluginOptions: {
        preset: [
            'default',
            {
                discardComments: { removeAll: true },
                mergeLonghand: false
            }
        ],

    },
    canPrint: true
})

使用时可能遇到类似的问题:my.oschina.net/MyItStudy/b…

差异化构建:多环境构建、单页多页应用混合构建

这句话的言外之意是一套构建方案适于各种项目:基于应用形态(单页、多页)、基于业务类型(A、B两个不同的项目)、基于发布环境(测试环境、线上环境)等

基于应用形态、业务类型构建项目

这两种场景可以综合为体现为模块化开发,创建合理的工程目录便可实现,如下目录结构:

  • src(源码目录)
    • assets(字体、图片、全局样式等)
      • font
      • image
      • style
    • entry (入口文件)
    • global (全局功能:统跳协议、用户信息读取更新等)
    • http (网络请求实现)
    • page (页面)
    • widget (基础组件)
    • router (页面路由实现)
    • storage(本地存储)
    • store (应用状态数据管理)
    • util (工具类)

基于发布环境多样性构建项目

这种情况考虑实际项目中的服务器环境有多套,还要考虑方便自动化构建工具的使用,我们可以通过区分 webpack配置来实现,如下构建配置:

  • build(构建目录)
    • env.config.js(发布环境配置)
    • webpack.config.base.js (通用webpack配置)
    • webpack.config.dev.js (本地开发webpack配置)
    • webpack.config.prd.js (发布环境webpack配置)

env.config.js中配置一些项目环境参数

const configA = {
    local: { // 本地开发
        BASE_URL: "http://xxx:8081",
        SERVER_ENV: "local",
        NODE_ENV: "development",
        PROXY_CONTEXT: ["/", "/system/api", "/media/api"]
    },
    prd: { // 发布模式-生产环境
        BASE_URL: "http://xxx:8081",
        SERVER_ENV: "prd",
        NODE_ENV: "production"
    }
}

package.json中的构建命令如下

  "serve": "cross-env NODE_ENV=development webpack-dev-server --hot --config build/webpack.config.dev.js",
  "build:dev": "cross-env SERVER_ENV=dev npm run build",
  "build:test": "cross-env SERVER_ENV=test npm run build",
  "build": "cross-env NODE_ENV=production webpack --config build/webpack.config.prd.js",

其中build:devbuild:test便是针对发布模式下各种环境的区分,这两种命令也可以提取为npm 命令参数比如叫senv,然后在env.config.js中接收这个参数,匹配config下localprd等属性。

对于多个项目(独立模块)开发的场景,在env.config.js添加新的项目配置

const configB = {
    local: { // 本地开发
        xxx
    },
    prd: { // 发布模式-生产环境
        xxx
    }
}

这时,需要为npm命令增加一个参数如starget,用来指定当前构建哪个项目(独立模块),从而确定使用哪套配置

const npmConfigArgV = JSON.parse(process.env.npm_config_argv)
const cooked = npmConfigArgV.cooked
console.log('命令参数列表', cooked)
let starget = ''
if (Array.isArray(cooked)) {
    const stIndex = cooked.indexOf('--starget')
    if (stIndex > -1) {
        starget = cooked[stIndex + 1]
    }
}

then choose configA or configB

适配要求:移动端自适应,浏览器兼容性

页面的自适应采用基于rem的适配方案,引入amfe-flexible

对于css的适配方案,采用postcss-loaderautoprefixerpostcss-pxtorem相结合的方案

入口文件引入
import 'amfe-flexible'

postcss配置

const postCssConfig = {
    loader: 'postcss-loader',
    options: {
        plugins: [
            require('autoprefixer'),
            require('postcss-pxtorem')(
                {
                    rootValue: 37.5,
                    propList: ['*']
                }
            )
        ]
    }
}

这样便可愉快地玩耍了,至于项目中使用vue或者react开发,可随意选择,当然在现有基础上还需要添加一些配置。对于vue,主要是添加vue-loader、vue-template-compiler,对于react,主要是添加@babel/preset-react,webpack配置如下:

  {
      test: /\.(js|jsx|ts|tsx)$/,
      use: [
          'thread-loader',
          'babel-loader'
      ],
      exclude: /node_modules/
  },
  {
      test: /\.vue$/,
      use: [
          'vue-loader'
      ],
  }