NextJs 是一款优秀的 react 同构直出框架,写一次代码,能够同时在服务端和浏览器端。这是因为 NextJs 会在服务端和浏览器分别打包一份,然后通过数据进行互通。
可是若一些模块只能在浏览器端使用,或只能在服务端使用,该怎么办呢?
1. 只在浏览器端使用的模块
例如@fingerprintjs/fingerprintjs
组件,在 3.0.3 版本及之前,若在 NextJs 中引用时,会直接报错。这是我之前提的 issue:https://github.com/fingerprintjs/fingerprintjs/issues/602。
例如:
COPYJAVASCRIPT
import FingerprintJS from '@fingerprintjs/fingerprintjs'; useEffect(() => { FingerprintJS.load(); }, []);
会提示 window 变量不存在的错误:
ReferenceError: window is not defined
这是因为,在 FingerprintJS 模块中,直接调用了 window 变量,而 window 变量只有在浏览器端才有。因此,引用模块后,啥也没干,就已经报错了。
解决方案,就是使用 import 异步引入:
COPYJAVASCRIPT
useEffect(() => { // 这里是浏览器的环境 import('@fingerprintjs/fingerprintjs') .then((FingerprintJS) => FingerprintJS.load()) .then((fp) => fp.get()) .then((result) => console.log(result.visitorId)); }, []);
这里也仅仅是用 FingerprintJS 模块来举个例子,不过该模块已经从 3.0.6 版本开始解决这个问题了。它会在调用 load 方法时,才会去引用 window 变量。
2. 只在服务端使用的模块
某组件 A 引用了 net 模块、stream 模块等只有在服务端才有的模块,这些模块就只能在服务端使用。
若直接 import 的话,NextJs 也会在浏览器端打包一份,但这个组件在浏览器端又无法使用。
我们公司一个很有名的名字服务-北极星模块,一个只在服务端使用的模块。开发的过程中,是没有感觉的,但打包时就会提示找不到模块:
有两种解决方案。
2.1 只在 server 端引用
NextJs 可以自定义 sever 文件,自定义 server 的文档:https://nextjs.org/docs/advanced-features/custom-server。
如果可以的话,我们把代码逻辑引导 sever 文件中。
COPYJAVASCRIPT
// server.js const { createServer } = require('http'); const { parse } = require('url'); const next = require('next'); const Polaris = require('@tencent/polaris'); // 服务模块在这里引用,就可以了 const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare().then(() => { createServer((req, res) => { // Be sure to pass `true` as the second argument to `url.parse`. // This tells it to parse the query portion of the URL. const parsedUrl = parse(req.url, true); const { pathname, query } = parsedUrl; if (pathname === '/a') { console.log(Polaris); app.render(req, res, '/a', query); } else if (pathname === '/b') { app.render(req, res, '/b', query); } else { handle(req, res, parsedUrl); } }).listen(3000, (err) => { if (err) throw err; console.log('> Ready on http://localhost:3000'); }); });
server.js 本身就是服务端的文件,它不会通过 NextJs 的打包系统引入到浏览器端。
2.2 在服务侧按需引入
若不方便将代码执行路径引导到服务端的文件。我们可以在服务侧按需引入,然后在 next.config.js 中配置不要打包到浏览器端。
COPYJAVASCRIPT
export default function App { }; export async function getStaticProps() { const Polaris = require('@tencent/polaris'); console.log(Polaris); return {}; }
getStaticProps()
方法在 NextJs 中,就是服务端运行的方法。我们在这里面使用require()
来按需引入。
接着在 next.config.js 中:
COPYJAVASCRIPT
const withAntdLess = require('next-plugin-antd-less'); module.exports = (phase) => withAntdLess({ webpack: (cfg, { isServer, webpack }) => { const config = cfg; if (!isServer) { // 在浏览器端,忽略这些模块的打包 const ignoreList = ['@tencent\\/polaris', 'dns', 'dotenv']; ignoreList.forEach((n) => { config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: new RegExp(n) })); }); } return config; }, });
在浏览器端,忽略这些模块的打包。
3. 总结
NextJs 是一个在服务端和浏览器都可以运行的 react 框架,我们在使用的使用要特别注意有哪些是只能在客户端使用,哪些是只能在服务端使用。