11月01, 2017

Next.js实战

Next.js是目前用于实现React服务端渲染框架中的比较流行的一个,得力于ZEIT的维护,使得相对于其它的一些框架,不论是在文档还是配套上面,它都比较齐全。

官方就提供了learnnextjs这样的交互式入门教程,这里就不多说了(结合文档效果更好)。

既然是实战,这里就说点自己遇到的问题及解决方案,下面将问题总的划分为几个分类:

JS方面的坑

使用jsx作为文件后缀?

一开始笔者习惯性的使用jsx作为文件后缀,发现控制台中老是报错,说是模块没有找到,这种一看就是配置问题,需要修改next.config.js中webpack的配置,增加extensionsjsx还有对应的babel-loader的配置,结果发现完全不起作用,看来是next.js中对于相关文件做了特殊的处理,目前来看只能使用*.js作为文件后缀了。

官方目前的回复也是这样,暂时就支持*.js作为后缀,具体可以看一下这个issue

JS中读取process.env.*返回undefined

如果直接用node执行*.js文件,那么cross-env就比较合适,这个包帮我们处理的跨平台时env定义的问题,但是next.js是依赖于构建工具webpack的,换句话说,如果直接在package.jsonscripts中使用cross-env是无法直接影响webpack的处理结果的,还需要用到webpack.DefinePlugin这个插件来做一次定义,next.js提供了一个简单的例子核心代码如下:

webpack: config => {
    config.plugins.forEach(plugin => {
      if (plugin.constructor.name === 'DefinePlugin') {
        plugin.definitions['process.env.SECRET'] = JSON.stringify(process.env.SECRET)
      }
    })

    return config
  }

另外,这只是其中的一个解决方法,next.js还提供了一个基于Babeldotenv的例子,原理就是在Babel处理阶段就将代码中的process.env.*替换处理。

使用babel处理node_modules中部分JS的方法?

为了兼容 Android4.3,就必须要用 babel 结合相关的工具对ES6做兼容性处理,但是,默认的next.config.js中关于jsx的处理一般都会设置exclude过滤掉node_modules,就像这样的配置:

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

next.js的issues中有不少关于这个的讨论,目前实践下来,最简单的就是直接修改exclude属性即可:

{
  test: /\.(js|jsx)$/,
  use: ['babel-loader'],
  // 这里对react及react-dom 2个做了过滤处理
  // 也就是说这2个会被babel做处理
  exclude: /node_modules\/(?!(react|react-dom)\/).*/
}

当然,babel还需要结合.babelrc.browserslistrc这些配置项及babel-plugin-transform-runtime这样的工具,如果需要了解更多,可以参考 Babel笔记 这篇文章。

Static静态资源方面的坑

官网教程中并没有全面的介绍如何控制好静态资源的加载及维护,下面就说说一些常见的问题。

如何引入图片、字体等静态资源?

webpack及配套的*-loader,使得我们可以很方便的在JS文件中通过import载入各种静态资源,不过next.js基础中并未对图片、字体等文件做特殊配置,所以需要我们手动在next.config.js中增加webpack的相关配置:

{
  test: /\.(png|jpe?g|gif)(\?.*)?$/,
  use: [{
      loader: 'emit-file-loader',
      options: {
        name: 'dist/[path][name].[ext]',
      },
    },
    {
      loader: 'url-loader',
      options: {
        limit: 2000,
        outputPath: 'static/images/',
        publicPath: '/_next/',
      },
    },
  ],
}, {
  test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
  use: [{
      loader: 'emit-file-loader',
      options: {
        name: 'dist/[path][name].[ext]',
      },
    },
    {
      loader: 'url-loader',
      options: {
        limit: 2000,
        outputPath: 'static/fonts/',
        publicPath: '/_next/',
      },
    },
  ],
}

如何引用全局静态资源?

官网的推荐是在根目录的static文件夹中放入即可,后续不论是在JS还是CSS文件中,只需要写绝对路径/static/{path_to_file}即可,next.js帮助做了一些类似于路由映射的处理,很遗憾,官方并未给出cache的方案,目前来看有2中思路

1. 基于URL中param的cache方案
<link
  rel="stylesheet"
  type="text/css"
  href={`/static/styles/app.css?${this.props.__NEXT_DATA__.buildStats['app.js']
    .hash}`}
/>
2. 基于URL中path结合脚本迁移文件路(暂时是个思路,未具体实现)
<link
  rel="stylesheet"
  type="text/css"
  href={`/_next/${this.props.__NEXT_DATA__.buildStats['app.js'].hash}/app.css`}
/>

文件迁移脚本

jq是一个轻量级的命令行JSON处理工具

#!/usr/bin/env bash
NEXT_BUILD_ID=`cat .next/build-stats.json | jq -r '.[][]'`
cp -Rf ./static/* ./_next/${NEXT_BUILD_ID}/

CSS方面的坑

官方提供了很多CSS的示例,但是个人觉得这个是踩坑最多的地方

实现CSS Modules

得益于css-loader,如果单单是做客户端渲染,只需要在webpack的配置中启用modules配置即可,从而简简单单的实现BEM,但是Next.js官方并不支持该方案,具体可以参考这个issue,虽然大家也都集思广益,造出了各种方案,但是具体实施起来效果都不怎么理想,反倒是官方推荐的styled-jsx方案,结合postcss之后,成了不错的选择。

with-styled-jsx-plugins这个例子已经实现了基础的配置,你只需要增加postcss.config.js扩展一下即可:

/**
 * postcss.config.js
 *
 * @see https://github.com/zeit/next.js/tree/master/examples/with-global-stylesheet/
 */
const postcssEasyImport = require('postcss-easy-import');
const postcssCssnext = require('postcss-cssnext');
const cssnano = require('cssnano');
const lost = require('lost');

const postCSSPluginCombinations = [
  // keep this first
  // https://github.com/TrySound/postcss-easy-import#options
  postcssEasyImport({
    // prefix: '_',
  }),
  lost(),
  postcssCssnext({
    // so imports are auto-prefixed too
    // @see http://robin-front.github.io/2016/04/09/postCSS-loader%E9%85%8D%E7%BD%AE/
    browsers: [
      '> 1% in CN',
      'last 2 versions',
    ],
    features: {
      rem: false,
    },
  }),
];

module.exports = {
  plugins:
    process.env.NODE_ENV === 'production'
      ? [...postCSSPluginCombinations, cssnano({
        autoprefixer: false,
        discardUnused: {fontFace: false}
      })]
      : postCSSPluginCombinations,
};

styled-jsx与babel的冲突?

之前在babel的生产环境发布前,会利用一些plugin优化react的产出代码,其中最常用的就是transform-react-remove-prop-typestransform-react-constant-elementstransform-react-inline-elements,这个时候 styled-jsx对于<style jsx>{***}</style>中的处理就会与transform-react-constant-elementstransform-react-inline-elements冲突了,导致无法正常处理scopeoptimized的功能,这里建议直接将transform-react-constant-elementstransform-react-inline-elements去除即可。

cssnano处理后@font-face内容缺失?

一般现在都会在上生产环境前使用cssnano对CSS文件做一次压缩优化,不过现在碰到一个问题,在处理后,@font-face的内容缺失了,cssnano的issue中有人提供了解决方式:禁用其对@font-face的优化即可。

cssnano({
  autoprefixer: false,
  // 禁用对`@font-face`的优化
  discardUnused: {
    fontFace: false,
  },
})

首次渲染pages/_document.js,全局未能生效?

next.js推荐使用styled-jsx处理css,所以在_document.js中想当然的使用了下面的写法:

<style jsx global>{mainStyles}</style>

这样就直接导致了一个问题,在第一次访问服务的时候,发现mainStyles中的内容并未被加载并执行,next.js的issues 中有人也已经反映过这个问题并且给出了解决方案,简单来说就是直接使用<style>加载即可,不要使用<style jsx global>

本文链接:https://itony.net/post/react-nextjs-war.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。