相信大家在使用 nextjs 的时候, 难免遇到一些坑. 其实可能大部分原因在于 nextjs 做了很多封装, 我们可能不能第一时间搞清楚包括它相关的所有配置, 比如其中的 webpack 配置. 我前面也写过 SSR 实现的文章和简单的轮子《实现 ssr 服务端渲染》, 也知道 SSR 要实现为 nextjs 这样的三方框架, 还是会需要经历很复杂编码的.
总归有时候遇到问题, 在网上也查不到一个正确的解决方案. 比如, 我为此头痛几天的 antd-mobile 按需加载, 最开始我无法正常使用, 就只能全局引入 antd-mobile 的 min.CSS, 这导致我要在页面加载 164k 的 CSS 文件, 我们使用 nextjs 就是为了提升加载速度, 这种情况不能忍啊!
言归正传, 先说说我遇到的问题, 我使用了 antd-mobile 并且需要对它进行按需加载, 下面是官方的文档, 推荐我们使用 babel-plugin-import.
按照 nextjs 官方文档我们在 .babelrc 中添加插件
- {
- "presets": ["next/babel"],
- "plugins": [
- ["import", { "libraryName": "antd-mobile", "style": true }]
- ]
- }
可当我运行的时候报错了, 报错如下. 最开始感到奇怪, 我并没有引入这个包, 后来发现其实是 antd-mobile 中有引入它. 但是为什么会报错呢, 便想到应该是 webpack loader 的问题, 我认为是 loader 排除了 node_modules.(这里解释一下, node_modules 中的包本应该都是打包好的, 但是一些情况下, 我们是直接引入源代码中的模块的, 那这样的话我们就需要让我们的 loader 解析 node_modules 中的代码, 然而有些默认配置就是排除了 node_modules 的, 这样就会导致无法解析).
然后我在 next.config.JS 中, 定义 webpack 方法, 打印出 webpack 配置. nextjs 中的 webpack 配置大致是引入了一个 next-babel-loader 这样的 loader, 而我们使用 next-CSS,next-Less 或者 next-Sass 等插件, 相关的 loader 会被 push 到 rules 中. 核心的 loader 就是 next-babel-loader. 然而我在其参数中并没有发现 exclude, 到是有 include, 而后我往 include 里添加 node_modules 下需要的组件正则, 发现并没有效果. 而后我经历了各种痛苦, 尝试过各种方面的办法, 网上也查不出解决方案. 好, 跳过心酸的部分.
再后来我开始仔细的一个个看官方的插件, 我找到了它: next-transpile-modules, 从名称上来看似乎和我想要的有点关系. https://github.com/martpie/next-transpile-modules .
一看文档果然, 它就是我要找的, 它就是解决 node_modules 中代码不被 loader 解析的问题. 我使用了它, 这时报错信息变了 (其实后来我弄比较清楚以后就没有报错了, 可能当时配置改的比较多, 哪里影响到了), 我觉得似乎起到作用了, 但是还是会报错. 于是我便看了一下它的代码, 我终于发现了 webpack.externals 这个配置, 原来是这个地方排除了解析外部依赖. 如果我们使用插件 transpile 并配置好 transpileModules: ["antd-mobile"],transpile 内部会生成 includes 正则, 在 externals 执行时, 会排除掉我们配置的 node_modules 模块, 因此 antd-mobile 就能被正常解析了, 代码如下
- if (config.externals) {
- config.externals = config.externals.map(external => {
- if (typeof external !== 'function') return external;
- return (ctx, req, cb) => {
- return includes.find(include =>
- req.startsWith('.')
- ? include.test(path.resolve(ctx, req))
- : include.test(req)
- )
- ? cb()
- : external(ctx, req, cb);
- };
- });
- }
而后它又添加了一个 next-babel-loader 到 rules 中, 现在其实有两个 next-babel-loader 在 webpack 配置中. 我认为这个配置是多余的, 并且就是之前我可能哪里没配置对, 这个多余的 loader 让我编译报错了, 我把它生成的多余 loader 删除才没有报错的.
最后在我完全能正常运行的时候, 还是尝试删除了它, 发现并没有报错, 因为从理论上来说, 这个重复的 loader 本身也没有用, 因此我给作者提了一个建议, 建议去掉这个新 loader, 对方说再认真看看. 这里:.
- // Add a rule to include and parse all modules
- config.module.rules.push({
- test: /\.+(JS|jsx|ts|tsx)$/,
- loader: options.defaultLoaders.babel,
- include: includes
- });
我当前使用的 next 是 8.x, 在 6.x 里, 我看了下它确实是用的 exclude 来排除的 node_modules, 到 8 以后改为 externals 了, 一定有它官方的道理吧. 如果你用的是 6.x, 你可以尝试修改 exclude, 不过建议大家都升级为 8 吧, 很平滑的.
第二个问题, 可能也是大家比较常见的, 那就是 cssModules. 官方代码是这样的
- // next.config.JS
- const withCSS = require('@zeit/next-css')
- module.exports = withCSS({
- cssModules: true,
- cssLoaderOptions: {
- importLoaders: 1,
- localIdentName: "[local]___[hash:base64:5]",
- }
- })
完全没有问题, 可以正常使用. 只是 antd-mobile 的 class 名称也被 cssModules 给改了, 但是组件 dom 中的 class 名称并没有被修改, 这样样式就不起作用了. ok, 没有问题, 这个简单, 我们使用 CSS-loader API 中的 options.getLocalIdent, 来控制修改 class 名称. 代码大致如下
- const cssLoaderGetLocalIdent = require("css-loader/lib/getLocalIdent.js");
- /*.....*/
- cssLoaderOptions: {
- localIdentName: "[local]___[hash:base64:5]",
- getLocalIdent: (context, localIdentName, localName, options) => {
- let hz = context.resourcePath.replace(context.rootContext, "");
- if (/node_modules/.test(hz)) {
- return localName;
- } else {
- return cssLoaderGetLocalIdent(
- context,
- localIdentName,
- localName,
- options
- );
- }
- }
- },
通过阅读 CSS-loader 源码, 发现其内部运行过程, 它内部有一个 CSS-loader/lib/getLocalIdent.JS 方法, 如果用户自定义了 getLocalIdent 方法, 它在编译 cssmodules 时, 便会用用户定义的方法, 否则使用自带的方法. 我的想法就是通过自定义 getLocalIdent, 正则判断 node_modules, 也就是当前样式如果是来自于 node_modules 中文件的话, 我返回它本身的名称, 就是不改动它, 而它是我们的源码的话, 我执行 CSS-loader 本身的 getLocalIdent 方法. 这样就既使我们自己的代码能被 cssmodules, 而三方库的代码不被 cssmodules 影响.
最后附上两个配置文件 .babelrc 和 next-config.JS
- //.babelrc
- {
- "presets": ["next/babel"],
- "plugins": [
- ["import", { "libraryName": "antd-mobile", "style": true }]
- ]
- }
- //next.config.JS
- const withLess = require("@zeit/next-less");
- const withCss = require("@zeit/next-css");
- const withPlugins = require("next-compose-plugins");
- const withTM = require('next-transpile-modules');
- const pxtorem = require("postcss-pxtorem");
- const cssLoaderGetLocalIdent = require("css-loader/lib/getLocalIdent.js");
- module.exports = withPlugins([withCss, withLess,withTM], {
- transpileModules: ["antd-mobile"],
- cssModules: true,
- cssLoaderOptions: {
- localIdentName: "[local]___[hash:base64:5]",
- getLocalIdent: (context, localIdentName, localName, options) => {
- let hz = context.resourcePath.replace(context.rootContext, "");
- if (/node_modules/.test(hz)) {
- return localName;
- } else {
- return cssLoaderGetLocalIdent(
- context,
- localIdentName,
- localName,
- options
- );
- }
- }
- },
- webpack(config, options) {
- //Less 设置 rem
- config.module.rules.push({
- test: /\.Less$/,
- exclude: /node_modules/,
- loader: "postcss-loader",
- query: {
- plugins: [
- pxtorem({
- rootValue: 50,
- unitPrecision: 5,
- propList: ["*"],
- selectorBlackList: [/^\.nop2r/],
- replace: true,
- mediaQuery: false,
- minPixelValue: 0
- })
- ]
- }
- });
- return config;
- }
- });
pxtorem 是转换 px 为 rem, 有的需要的自取, 如果此方案解决了你的问题, 点个赞吧~
来源: https://www.cnblogs.com/1wen/p/10793868.html