nextjs 写 css loader 处理多地区不同基础变量的方法

Posted danhuang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nextjs 写 css loader 处理多地区不同基础变量的方法相关的知识,希望对你有一定的参考价值。

由于项目在多地区进行发布,为了复用,主工程使用同一个,但是这样会带来一个问题,由于地区的设备分布不同,以及当地的字体选择不一样,从而导致了 global 中的一些熟悉无法复用,而且必须配置两套,那么如何来解决这个问题呢?

解决思路方法

由于项目中有一个非常基础的变量模块,暂且叫做 basic.scss ,然后在很多 scss 文件中都对该文件进行了引用,现在需要区分多个地区的基础配置,那么直接复制一份 basic.scss ,命名为 basic-[country].scss ,接下来就是要找到引用 basic.scss 的地方,然后在打包的时候将其替换为 basic-[country].scss 具体的国家或者地区就可以了。

那么这里就有问题是,怎么去找到这个文件,并且把引用关联找到,最后再替换,这种打包类的问题,当然 webpack 是首选。

webpack 选择

一开始思路是使用 webpack 来解决这个问题,那么到底是使用 loader 还是 plugin 呢?这里就需要去思考 loader 和 plugin 的区别。

这里引用一段说明:

作用不同

  • Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非javascript文件的能力。
  • Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

用法不同

  • Loader在module.rules中配置,也就是说作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
  • Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

其实看到第一段就有答案了,webpack 原生是只能解析 js 文件,如果想要其他文件也打包的话,就需要使用到 loader ,所以这里我们选择使用 loader 来处理。

loader 插件选择

在 loader 插件中有一个插件一下进入了视野,那就是 string-replace-loader 我们看下他的一个例子:

module.exports = 
  // ...
  module: 
    rules: [
      
        test: /fileInWhichJQueryIsUndefined\\.js$/,
        loader: 'string-replace-loader',
        options: 
          search: '$',
          replace: 'window.jQuery',
        
      
    ]
  

用法也比较简单,使用正则查询需要处理的文件,然后使用 string-replace-loader 来处理,参数第一个 search 查询需要替换的字符串,第二个是需要替换成的字符串。

想到这里,那么直接修改为下面这个方式不就可以了吗?

module.exports = 
  // ...
  module: 
    rules: [
      
        test: /[\\s][\\S]\\.scss$/,
        loader: 'string-replace-loader',
        options: 
          search: 'basic.scss',
          replace: 'basic-[country].scss',
        
      
    ]
  

如果你自己写的 webpack 插件确实是这样就行了,但是由于我们使用的是 nextjs 框架,webpack 是自动生成的,因此我们需要看看 nextjs 如何应用。

nextjs 接入

nextjs 官方有提供 loader 或者 plugin 的写法,以下是官方的例子

module.exports = 
  webpack: (config, options) => 
    config.module.rules.push(
      test: /\\.mdx/,
      use: [
        options.defaultLoaders.babel,
        
          loader: '@mdx-js/loader',
          options: pluginOptions.options,
        ,
      ],
    )

    return config
  ,

按照官网的例子,我们修改下,改为下面这种 scss 替换的方式

module.exports = 
  webpack: (config, options) => 
    config.module.rules.push(
      test: /[\\s][\\S]\\.scss$/,
      use: [
        
          loader: 'string-replace-loader',
          options: 
            search: 'basic.scss',
            replace: 'basic-[country].scss',
          
        ,
      ],
    )

    return config
  ,

修改完成以后,运行一下,你会发现解析其中会报错,而且要不就是说 css 解析不了,要不就是 less 解析不了,要不就是 js 问题等等。

看了下原因才发现,我们不能直接的 push 进去,而是应该在现有的 rules 的规则中增加该 loader ,那么接下来我们就来解决这个问题。

首先我们写一个方法,来添加这个 loader 规则:

 const addUserLoader = function(arr)
          if(!(arr instanceof Array))return arr;
          arr.push(
            
              loader: 'string-replace-loader',
              options: 
                search: 'basic.scss',
            		replace: 'basic-[country].scss',
              
            
          );
          return arr;
        ;

接下来,我们需要去遍历当前所有的 rule 规则,我们先把所有的 rule 规则都加上这个,看看有没有问题,代码如下。

webpackConfig.module.rules.forEach(
      rule => 
        if(rule.oneOf instanceof Array)
          let cssRules = [];
          rule.oneOf.forEach(cssRule => 
            if(cssRule.use instanceof Array)
              addUserLoader(cssRule.use);
             else if(cssRule.use)
              addUserLoader([cssRule.use])
            
            cssRules.push(cssRule);
          );
          rule.oneOf = cssRules;
        
    
        newRules.push(rule);
    );
    webpackConfig.module.rules = newRules;
    return webpackConfig;

代码逻辑是比较简单的,遍历 rules ,rules 中 oneOf 非数组的不处理,数组的则进行遍历,判断 rule 下的 use 是否为数组,如果不是数组,说明是单个 loader ,那么先转化为数组,然后添加该 loader,如果是数组则直接 push 进去就可以了。

再运行一下,这样确实完成了,好了那么是否可以进一步优化呢?当然可以

优化方向

首先想到的是,我们不需要每个都增加该 loader,只需要正则能匹配 scss 结尾和 .global.scss 结尾的文件就可以了,认真看 nextjs 的 rules ,其中包含了一些以 .scss 结尾的规则和以 .global.scss 结尾以及不包含 .global 但是以 .scss 结尾的规则,那么这里有三个规则。

/\\.(css|less|scss|sass)$/
/(?<!\\.global)\\.(scss|sass)$/
/\\.global\\.(scss|sass)$/
[ /\\.global\\.css$/, /\\.global\\.less$/, /\\.global\\.(scss|sass)$/ ]

为了适应这些规则,我们写一个方法一些判断就可以了,代码如下。

const checkNeedReplace = function(ruleTest)
     if(!ruleTest)
       return false;
     
     if(!(ruleTest instanceof Array))
       ruleTest = [ruleTest];
     
     return ruleTest.some(rule => 
       if(rule.test('.global.scss') || rule.test('test.scss'))
         return true;
       
     );
   

由于 rules 也有是数组,有些不是,因此统一转化为数组,然后数组循环处理,判断是否存在一项满足条件的,使用 .global.scss 和 test.scss 去匹配,如果匹配就满足上面的正则条件。

有了上面方法,接下来我们只需要加一层过滤就可以了,代码如下。

webpackConfig.module.rules.forEach(      rule =>         if(rule.oneOf instanceof Array)          let cssRules = [];          rule.oneOf.forEach(cssRule =>             if(!checkNeedReplace(cssRule.test)) // 过滤不需要的规则              cssRules.push(cssRule);              return;                        console.log(cssRule.test);            if(cssRule.use instanceof Array)              addUserLoader(cssRule.use);             else if(cssRule.use)              addUserLoader([cssRule.use])                        cssRules.push(cssRule);          );          rule.oneOf = cssRules;                    newRules.push(rule);    )

以上就完成了,那么最后我们再来看一下全部代码

webpack: (webpackConfig,  dev, isServer, buildId, defaultLoaders ) =>     let newRules = [];    const addUserLoader = function(arr)      if(!(arr instanceof Array))return arr;      arr.push(                  loader: 'string-replace-loader',          options:             search: 'vars.scss',            replace: 'vars-eurp.scss'                        );      return arr;    ;    const checkNeedReplace = function(ruleTest)      if(!ruleTest)        return false;            if(!(ruleTest instanceof Array))        ruleTest = [ruleTest];            return ruleTest.some(rule =>         if(rule.test('.global.scss') || rule.test('test.scss'))          return true;              );        webpackConfig.module.rules.forEach(      rule =>         if(rule.oneOf instanceof Array)          let cssRules = [];          rule.oneOf.forEach(cssRule =>             if(!checkNeedReplace(cssRule.test)) // 过滤不需要的规则              cssRules.push(cssRule);              return;                        console.log(cssRule.test);            if(cssRule.use instanceof Array)              addUserLoader(cssRule.use);             else if(cssRule.use)              addUserLoader([cssRule.use])                        cssRules.push(cssRule);          );          rule.oneOf = cssRules;                    newRules.push(rule);    );    webpackConfig.module.rules = newRules;    return webpackConfig;  

如果你还有其他问题,欢迎一起交流学习。

参考文章:https://segmentfault.com/a/1190000037712654

以上是关于nextjs 写 css loader 处理多地区不同基础变量的方法的主要内容,如果未能解决你的问题,请参考以下文章

nextjs 写 css loader 处理多地区不同基础变量的方法

nextjs 写 css loader 处理多地区不同基础变量的方法

webpack基础

webpack loader

【Webpack4】CSS 配置之 postcss-loader

使用 NextJS 和 Next-CSS 做出反应:您可能需要适当的加载器来处理这种文件类型