在小程序中愉快地写scss

1,578 阅读2分钟

利用webpack将sass编译成wxss插入小程序页面

公司的电商小程序项目有点年头了,一直用的原生写法,由于种种原因,没法用其他技术栈重构,每次写wxss都是很难受的,所以有了这个想法

问题点1:小程序每个页面都有一个wxss文件,所以webpack打包编译如何输出多个wxss文件

wepback设置多入口配置,会对应输出多出口文件。 所以项目中有多少个scss文件,就对应多少个入口。

  1. app.json中定义了主包和分包的页面路径,直接遍历获取他们的目录名
// pages and subPackages
const { pages, subPackages } = appJson

// 提取主包目录名
// "pages/homeDelivery/index" => "pages/homeDelivery"
// /[^/]+(?!.*\/)/ 把最后一个斜杠后的内容去掉
const dirList = pages.map(page => page.replace(/[^/]+(?!.*\/)/, ''))

// 提取分包目录名
// "pages/paySuccess/index" => "fightGroups/pages/paySuccess/index" => "fightGroups/pages/paySuccess"
subPackages.forEach(sub => {
  const { pages, root } = sub
  dirList.push(...pages.map(subPage => `${root}/${subPage}`.replace(/[^/]+(?!.*\/)/, '')))
})

再通过遍历目录下的文件获取所有scss/sass文件

  1. 再加上components组件目录,递归遍历此目录下所有文件,如果是scss/sass就返回

  2. 提取wxss文件: MiniCssExtractPlugin

plugins: [
  // ...
  new MiniCssExtractPlugin({
    // 重新定义后缀名
    filename: '[name].wxss'
  })
]

问题点2: webpack打包会默认输出一个 [name].js文件,但我们不需要

FixStyleOnlyEntriesPlugin利用这个插件,会删除webpack打包出来的[name].js,并且不会影响原有的js文件

问题点3:热更新

开启webpack的watch功能

watchOptions: {
  // 忽略除scss之外的所有文件,减轻监听压力
  ignored: [/node_modules/, '*.js', '*.css', '*.wxml', '*.wxss', '*.wxs', '*.json']
},

此时修改scss文件,就会重新打包编译,实现热更新。 提供两个shell命令,分别用于一次性构建和热更新

"build": "cross-env build_type=buildOnce webpack --config webpack.config.js",
"watch": "cross-env build_type=buildConstantly webpack --config webpack.config.js"

问题点4:小程序的打包编译

  1. 添加.gitignore忽略node_modules
  2. project.config.json通过packOptions设置打包配置,我们需要忽略node_modules,webpack.config.js,package.json,package-lock.json
  3. project.config.json通过packOptions设置热更新,设置同上
"packOptions": {
    "ignore": [
        {
            "type": "folder",
            "value": "node_modules"
        },
        {
            "type": "file",
            "value": "webpack.config.js"
        },
        {
            "type": "file",
            "value": "package.json"
        },
        {
            "type": "file",
            "value": "package-lock.json"
        }
    ]
},
"watchOptions": {
    "ignore": [
        "webpack.config.js",
        "package.json",
        "package-lock.json",
        "node_modules/**/**"
    ]
},

完整webpack配置

const path = require('path')
const fs = require('fs')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries");
const appJson = require('./app.json')

const baseConfig = {
  mode: 'development',
  watch: process.env.build_type === 'buildConstantly',
  watchOptions: {
    // 忽略除scss之外的所有文件
    ignored: [/node_modules/, '*.js', '*.css', '*.wxml', '*.wxss', '*.wxs', '*.json']
  },
  module: {
    rules: [
      {
        test: /\.s(a|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader, 'css-loader','sass-loader'
        ],
      }
    ],
  },
  plugins: [
    new FixStyleOnlyEntriesPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].wxss'
    })
  ]
}

const genExportInfo = (parseFileList) => {
  if (!parseFileList.length) return []
  return parseFileList.map(pathInfo => {
    return {
      entry: {
        index: path.resolve(__dirname, pathInfo),
      },
      output: {
        path: path.resolve(__dirname, path.dirname(pathInfo))
      },
      ...baseConfig,
    }
  })
}

/**
 * @desc 递归获取目录下的所有文件
 * @param {String} dir 指定目录路径
 * @param {RegExp} suffixReg 指定后缀名正则,/^.*$/表示匹配全通过
 * @param {Array} list 
 * @returns {Array} 返回文件路径
 */
const recursiveFile = (dir, suffixReg = /^.*$/, list = []) => {
  const readList = fs.readdirSync(dir)
  readList.forEach(r => {
    const filePath = path.join(dir, r)
    if (fs.statSync(filePath).isFile()) {
      const fileSuffix = path.extname(filePath).slice(1)
      suffixReg.test(fileSuffix) && list.push(filePath)
    } else {
      recursiveFile(filePath, suffixReg, list)
    }
  })
  return list
}

/**
 * @desc css文件路径来源: app.json里的pages和subPackages,还有components
 */
const genCssPathList = (appJson) => {
  // pages and subPackages
  const { pages, subPackages } = appJson

  // 提取主包目录名
  // "pages/homeDelivery/index" => "pages/homeDelivery"
  // /[^/]+(?!.*\/)/ 把最后一个斜杠后的内容去掉
  const dirList = pages.map(page => page.replace(/[^/]+(?!.*\/)/, ''))

  // 提取分包目录名
  // "pages/paySuccess/index" => "fightGroups/pages/paySuccess/index" => "fightGroups/pages/paySuccess"
  subPackages.forEach(sub => {
    const { pages, root } = sub
    dirList.push(...pages.map(subPage => `${root}/${subPage}`.replace(/[^/]+(?!.*\/)/, '')))
  })

  const needToParseList = []

  // 提取components所有文件
  needToParseList.push(...recursiveFile(path.resolve(__dirname, './components'), /s(a|c)ss/))
  
  dirList.forEach(dir => {
    needToParseList.push(...recursiveFile(path.resolve(__dirname, dir), /s(a|c)ss/))
  })

  return needToParseList
}

module.exports = genExportInfo(genCssPathList(appJson))