打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
初次搭建Next.js项目历程(精)

使用create-next-app初始化项目

create-next-app和react官方提供的create-react-app(CRA)非常像,next.js官方也自带了create-next-app(CNA),所以直接安装就好。

sudo yarn global add create-next-app

安装好之后就可以创建项目了

create-next-app project-name

生成之后的目录结构如下

    |-- .gitignore    |-- package.json    |-- yarn.lock    |-- components    |   |-- nav.js    |-- pages    |   |-- index.js    |-- public        |-- favicon.ico

我们可以查看下package.json

package.json

{  "name": "next-demo4",  "version": "0.1.0",  "private": true,  "scripts": {    "dev": "next dev",    "build": "next build",    "start": "next start"  },  "dependencies": {    "next": "9.1.4",    "react": "16.12.0",    "react-dom": "16.12.0"  }}

然后执行yarn dev运行起来试试

配置koa路由

安装好了之后,我们可以设置成koa路由,首先添加koa 和 koa-router依赖包,然后在根目录下添加server.js文件,如下:

yarn add koa koa-router

server.js

const Koa = require("koa");const next = require("next");const Router = require("koa-router");const port = parseInt(process.env.PORT, 10) || 3003;const dev = process.env.NODE_ENV !== "production";const app = next({ dev });const handle = app.getRequestHandler();app.prepare().then(() => {  const server = new Koa();  const router = new Router();  router.get("/", async ctx => {    await app.render(ctx.req, ctx.res, "/", ctx.query);    ctx.respond = false;  });  router.all("*", async ctx => {    await handle(ctx.req, ctx.res);    ctx.respond = false;  });  server.use(async (ctx, next) => {    ctx.res.statusCode = 200;    await next();  });  server.use(router.routes());  server.listen(port, () => {    console.log(`> Ready on http://localhost:${port}`);  });});

修改package.json文件,添加如下代码:

"scripts": {    "dev": "next dev",    "build": "next build",    "start": "next start",+   "koa": "node server.js -p 3003"  },

我们现在执行yarn koa试试,运行成功!

通过next.js官方提供的example案例来配置redux和immutable.js

next.js官方案例提供了周边框架的搭建实例,方便开发人员配置相关框架和插件,如下所示:

首先我们先来安装redux相关依赖包,如下

yarn add redux redux-actions redux-saga redux-logger

接着我们找一下我们要搭建的redux和immutable.js相关实例:with-redux-saga和with-immutable-redux-wrapper,然后自己一点点的去配置,当然中间肯定遇到了各种各样的问题,这里就不详细说明了,只展示配置好运行起来没问题的代码分享给大家,如下所示:

_app.js

import React from "react";import { Provider } from "react-redux";import App, { Container } from "next/app";import withRedux from "next-redux-wrapper";import withReduxSaga from "next-redux-saga";import { fromJS } from "immutable";import configureStore from "../service/redux/store";import "../assets/styles/index.css";import "antd/dist/antd.css";@withRedux(configureStore)@withReduxSagaclass NextApp extends App {  static async getInitialProps({ Component, ctx }) {    let pageProps = {};    if (Component.getInitialProps) {      pageProps = await Component.getInitialProps({ ctx });    }    return { pageProps };  }  render() {    const { Component, pageProps, store } = this.props;    return (      <Container>        <Provider store={store}>          <Component {...pageProps} />        </Provider>      </Container>    );  }}export default NextApp;

store.js

import { createStore, compose, applyMiddleware } from "redux";import createSagaMiddleware from "redux-saga";import logger from "redux-logger";import rootReducer from "./reducers";import rootSaga from "./sagas";import { fromJS } from "immutable";// create the saga middlewareconst sagaMiddleware = createSagaMiddleware();// 组合middlewareconst middleWares = [sagaMiddleware, logger];export default function configureStore(preloadedState = fromJS({})) {  const store = createStore(rootReducer, applyMiddleware(...middleWares));  store.sagaTask = sagaMiddleware.run(rootSaga);  return store;}

reducers.js

import { combineReducers } from "redux";import { autoCombineReducer } from "../../utils/autoCombineRedux";export default combineReducers(autoCombineReducer());

sagas.js

import { all, fork } from "redux-saga/effects";import { autoCombineSaga } from "../../utils/autoCombineRedux";/*添加对action的监听 */export default function* rootSaga() {  yield all(autoCombineSaga());}

这里我说明下reducers.js和sagas.js用到的方法:autoCombineReducer和autoCombineSaga,以前我们写reducer.js或者saga.js是把相关reducer或者saga文件import进来,然后注入到combineReducers()方法或者yield all()里面,好处是直观,坏处就是每次写一个reducer或者saga文件就要在这里加一下,是不是觉得非常麻烦,索性自己写一个自动注册进combineReducers()或者yield all()的方法,这个方法利用webpack的CONTEXT(require.context)把文件夹下名称为reducer.js或者saga.js文件自动加载进来,详细请看下面代码:

autoCombineRedux.js

import { all, fork } from "redux-saga/effects";// 查询所有文件夹下的所有文件名,以文件名数组形式返回const getContext = (path, type) => {  let CONTEXT = type == "reducer" ? require.context(".", true, /reducer\.js$/) : require.context(".", true, /saga\.js$/);  if (path == "../") {    CONTEXT = type == "reducer" ? require.context("../", true, /reducer\.js$/) : require.context("../", true, /saga\.js$/);  }  if (path == "../../") {    CONTEXT = type == "reducer" ? require.context("../../", true, /reducer\.js$/) : require.context("../../", true, /saga\.js$/);  }  return CONTEXT;};export const autoCombineReducer = (path = "../") => {  let CONTEXT = getContext(path, "reducer");  // 获取完组合成reducer对象  const importReducer = req => (obj, path) => {    //从路径中获取reducer name    // output:[0: "demo",groups: undefined,index: 7,input: "./demo/reducer.js"]    const [componentName] = path.match(/\w+(?=\/reducer\.js$)/);    // 组合reducer对象    const reducer = {      [`${componentName}Reducer`]: req(path).default    };    return Object.assign({}, obj, reducer);  };  // 从CONTEXT路径数组中组合成reducer对象返回  const getReducers = ctx => ctx.keys().reduce(importReducer(ctx), {});  return getReducers(CONTEXT);};export const autoCombineSaga = (path = "../") => {  let CONTEXT = getContext(path, "saga");  // 获取完组合成reducer对象  const forkSaga = req => path => {    //从路径中获取reducer name    // output:[0: "demo",groups: undefined,index: 7,input: "./demo/demo.reducer.js"]    const [componentName] = path.match(/\w+(?=\/saga\.js$)/);    return fork(req(path).default);  };  // 从CONTEXT路径数组中组合成reducer对象返回  const getSagas = ctx => ctx.keys().map(forkSaga(ctx));  return getSagas(CONTEXT);};

大家有没有发现其实上面已经配置好了immutable.js,所以我们就可以直接在reduer文件里面使用immutable对象了,如下所示:

reducer.js

import { handleActions } from "redux-actions";import { authTypes } from "./action";import moment from "moment";import { Map, fromJS, merge } from "immutable";const initState = fromJS({  user: null,  token: ""});const authReducer = handleActions(  {    [authTypes.AUTH_SUCCESS]: (state, action) => {      return state.merge({        user: action.data.user,        token: action.data.token      });    },    [authTypes.SIGN_OUT]: (state, action) => {      return state.merge({        user: null,        token: ""      });    }  },  initState);export default authReducer;

这样就完成了redux和immutable.js的配置了,下面我们来配置antd。

使用next-plugins来配置antd和hiynn-design

ant-design
antd 就不需要我多说明了吧,国内顶级大厂的杰出之作。

hiynn-design
hiynn-design是我公司(海云)前(我)端(创)团(建)队(的)创建的UI库,分为标准化组件和可视化组件。

同样的我们在next.js的example里面找到:with-ant-design-less 我们就100%参考这个实例来配置antd,首先我们来添加下相关依赖包:

yarn add @zeit/next-css @zeit/next-sass @zeit/next-less next-compose-plugins less-vars-to-js next-images yarn add babel-plugin-import @babel/plugin-proposal-decorators @babel/plugin-proposal-do-s @babel/plugin-proposal-class-properties

回过头来我们再来看看next-plugins包含那些插件,如下所示:

这里就不一一详细说明了,我们只举几个重要的安装包来说明下。

next-images
在SPA项目里面我们使用import或者require来加载图片,但是next.js不是那么简单的,它会提供你一个public文件夹来放静态图片,然后写上绝对地址,这样一来动态添加图片就变得麻烦,因此就有next-images这个依赖包的出现了,这个包的作用是让你像import require那样引入你的图片。

next-compose-plugins
当我们的next.config.js文件需要同时配置多个包的时候就会让每个单独配置的包分散开了,这样不利于维护,因此就出现了next-compose-plugins把各个分散的包集合在一起,具体的请查看next-compose-plugins

接着我们就按照with-ant-design-less来配置.babelrc和next.config.js文件,这里我们同时支持了ant-design和hiynn-design,详细代码如下:

.babelrc

{  "presets": [    "@babel/preset-react", "next/babel"  ],  "plugins": [    "@babel/plugin-proposal-do-s",    [      "@babel/plugin-proposal-decorators",      {        "legacy": true      }    ],    ["@babel/plugin-proposal-class-properties", {      "loose": true    }],    //按需加载antd样式    [      "import", {        "libraryName": "antd",        "style": true      },      "antd"    ],    //按需加载hiynnd样式    [      "import",      {        "libraryName": "hiynn-design",        "style": true      },      "hiynn-design"    ]  ]}

next.config.js

const withCss = require("@zeit/next-css");const withSass = require("@zeit/next-sass");const withPlugins = require("next-compose-plugins");const withLess = require("@zeit/next-less");const lessToJS = require("less-vars-to-js");const fs = require("fs");const path = require("path");const withImages = require("next-images");// Where your antd-custom.less file livesconst themeVariables = lessToJS(fs.readFileSync(path.resolve(__dirname, "./src/assets/styles/antd-custom.less"), "utf8"));module.exports = withPlugins([withCss, withSass, withLess, withImages], {  cssLoaderOptions: {    importLoaders: 1  },  lessLoaderOptions: {    javascriptEnabled: true,    modifyVars: themeVariables // make your antd custom effective  },  webpack: (config, { isServer }) => {    if (isServer) {      const antStyles = /antd\/.*?\/style.*?/;      const hiynnStyles = /hiynn-design\/.*?\/style.*?/;      const origExternals = [...config.externals];      config.externals = [        (context, request, callback) => {          if (request.match(antStyles) || request.match(hiynnStyles)) {            return callback();          }          if (typeof origExternals[0] === "function") {            origExternals[0](context, request, callback);          } else {            callback();          }        },        ...(typeof origExternals[0] === "function" ? [] : origExternals)      ];      config.module.rules.push(        {          test: antStyles,          use: "null-loader"        },        {          test: hiynnStyles,          use: "null-loader"        }      );    }    return config;  }});

请注意,Next.js无法使用css-loader。 请参阅官方页面上的警告
警告:不建议添加加载程序以支持新的文件类型(css,less,svg等),因为只有客户端代码通过webpack捆绑在一起,因此在初始服务器渲染中不起作用。 Babel插件是一个不错的选择,因为它们在服务器/客户端渲染之间一致地应用

这也算是配置next.js中的一个小插曲吧,大家知道就好,因为我们上面已经解决了这个问题。

Bug:Module parse failed: Unexpected character '@'

然后我们运行起来试试,报了一个bug!我们明明已经按照with-ant-design-less配置好了同时支持ant-design和hiynn-design为什么还报错?

接着我就找问题原因,在segmentfault和next-plugins 的issues 上面提问,最后发现next.js不支持.pcss,因此我就把hiynn-design的库从.pcss全部变成了.scss,这样重新编译并打包发布并安装,运行起来就没这个错了,但是又有个新的bug,真的是一波未平一波又起啊!

Bug:SyntaxError:Unexpected string

接着我就又找问题原因,同样我又在segmentfault和next-plugins的issues上面提问,最后发现是因为我两个库的.babelrc文件里面按需加载的libraryDirecory不一致导致的,hiynn-design库使用的libraryDirecory是'es',而next.js项目使用的libraryDirecory是'lib',然后我统一了一下两个libraryDirecory就没问题了,代码如下:

[  "import", {    "libraryName": "antd",    "style": true  },  "antd"],

Bug:SyntaxError:Invalid or unexpected token


这个问题是因为antd .less文件后缀引起的,在next.config.js加上下面这段代码就OK了

next.config.js

const withCss = require("@zeit/next-css");const withSass = require("@zeit/next-sass");const withPlugins = require("next-compose-plugins");const withLess = require("@zeit/next-less");const lessToJS = require("less-vars-to-js");const fs = require("fs");const path = require("path");const withImages = require("next-images");// Where your antd-custom.less file livesconst themeVariables = lessToJS(fs.readFileSync(path.resolve(__dirname, "./src/assets/styles/antd-custom.less"), "utf8"));+ require.extensions[".less"] = () => {};module.exports = withPlugins([withCss, withSass, withLess, withImages], {  cssLoaderOptions: {    importLoaders: 1  },  lessLoaderOptions: {    javascriptEnabled: true,    modifyVars: themeVariables // make your antd custom effective  },  webpack: (config, { isServer }) => {    if (isServer) {      const antStyles = /antd\/.*?\/style.*?/;      const hiynnStyles = /hiynn-design\/.*?\/style.*?/;      const origExternals = [...config.externals];      config.externals = [        (context, request, callback) => {          if (request.match(antStyles) || request.match(hiynnStyles)) {            return callback();          }          if (typeof origExternals[0] === "function") {            origExternals[0](context, request, callback);          } else {            callback();          }        },        ...(typeof origExternals[0] === "function" ? [] : origExternals)      ];      config.module.rules.push(        {          test: antStyles,          use: "null-loader"        },        {          test: hiynnStyles,          use: "null-loader"        }      );    }    return config;  }});

最后这些问题解决后我们再运行就不再报错了,运行正常!虽然我写这篇文章只用了2个小时,但是解决这些问题我足足花了一个多月。

总结

1、next.js对css-loader支持不友好,应该是所有node.js基于后台的通病了
2、配置redux和immutable网上有现成的案例可以根据这些案例进行配置
3、配置antd和hiynnd是最辛苦的,虽然最后成功了
4、postcss一般做为辅助sass或者less库使用最好,而不要单独使用,比如我的hiynn-design库一开始使用的全是.pcss结尾,然后就变成.scss。

参考

less、sass和postcss总结
next-plugins
ModuleParseError: Module parse failed: Unexpected character '@'
SyntaxError: Unexpected string
nextjs-starter-kit
nextjs-starter
哔哩哔哩(B站)的前端之路
PostCSS深入学习: PostCSS和Sass、Stylus或LESS一起使用
Next.js 使用指南2-路由与加载
next-images
next.js 采坑录(服务端渲染)

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
React+electron项目搭建 打包
Web 前端单元测试到底要怎么写?
react-app-rewired
react+taro-JYwebApp模板集成方案项目搭建【1】
渐进式入门教程 | Taro 文档
react 添加 react-redux 基本用法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服