带你趟坑儿 nuxt3@3.0.0 generate

大家好,我是右子。

nuxt3现在已经正式发布nuxt@3.0.0版本了,之前的都是rc版本,很多功能相比之下比较稳定。

最近想把站点的文章页做一下SSG(Static Site Generation),也为了网站提升缓存使用率和SEO优化。

过程中遇到了一些坑,在这里也分享一下我的趟坑之旅。

前言

工具准备

  • nuxt3
  • node
  • shell
  • webpack
  • vscode

安装nuxt3

执行以下命令

# 使用npx安装
$ npx nuxi init <project-name> 

# 如果用pnpm安装
$ pnpm dlx nuxi init <project-name>

你可能因为网络的问题无法完成下载,那么你也可以到这里下载zip包。

nuxt3的教程不多说,去官网看就行,目前中文版还不全面,建议去官网看:https://nuxt.com/。

安装node

node官网下载:https://nodejs.org/zh-cn/
选择稳定版本即可。

安装完node你就会拥有npm、npx,如果你想使用pnpm,可以通过npm命令安装pnpm。

正题开始

原本我有个人博客,随着vue3的推广并且版本的稳定,我的博客也重构了一下。内容页相关的数据,迁移到了阿里云的RDS和OSS里。
本着前端的“同构”理念:一套代码多端使用。所以选择了nuxt3来完成SSG的工作。

首先我的基于webpack5的前端工程:

image.png

我的想法是这样的:

graph TD
中台 --> 工具平台 --> 前端工程
工具平台 --> nuxt3工程

在中台场景中,开发效能工具,用于解决工程与工程之间的协作。

功能类比:代码转译(es6->es5/vue code->minipragram code),反编译,文件类型转换等等。

我的 主要目标 是站点 文章做SSG

下面我们来拆解的看下每一步如何做。

我需要我的工具可以将vue工程的项目文件,一键转到nuxt工程中。
在我的工具平台中操作:

image.png

会生成配置:

{
    // 我的vue工程Git地址
    project: git@github.com:host166/powerful.git
    // 我的nuxt工程Git地址
    target: git@github.com:host166/nuxt3.git,
    // 功能之一:拷贝
    mode: "copy"
}T

我的程序会根据 mode 类型进行工作,大致如下。

cd /Users/**/**/works
$ git clone git@github.com:host166/powerful.git
$ git clone git@github.com:host166/nuxt3.git
$ cp -rf powerful/pages powerful/components powerful/shared powerful/composables  powerful/assets nuxt3
$ rm -rf powerful
# 处理一些语法转译,比如<router-view />转成<NuxtPage />、去掉一些import引入等等
$ node astVue2Nuxt.js 
$ cd nuxt3
$ pnpm i

我这里用AST(abstract syntax code)做的处理,代码就补贴出来了,工具用的是:esprimaestraverse
不知道AST是什么的小伙伴可以搜索一下,这里推荐一个在线看ast结构的网站;

我就得到了一个nuxt3工程目录:

image.png

接下来,打开 package.json 可以看到 generate 命令。

{
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",  // 生成静态页面用的
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare"
  }
}

nuxt3有一个规则:“约定大于规范”。他的路由规则是pages文件夹里存在文件的话,在 npm dev 可以通过 local/路径访问。

image.png

接下来我需要把数据库中的文章数据导入到nuxt的pages里,让他可以构建打包出我想要的静态资源,我的方案如下:

// 1. 在js中获取100条列表数据,得到文章的id号;
// 2. 生成pages/atricle/下对应[id].vue文件;

const needle = require("needle");

let listUrls = [...]; // 请求列表的url地址,是一个集合。最好是分页获取,不然数据太大了内存回满。
let sh = [];

function concurrencyRequest(urls, maxNum=1) {
  // 验证类型
  if (!Array.isArray(urls)) {
    throw "urls must be an array type";
  }

  return new Promise((resolve) => {
    if (!urls.length) {
      resolve([]);
      return;
    }
    const results = [];
    let page = 0;  // 请求的下标志
    let count = 0; // 当前请求的完成数量

    async function req() {
      // 请求结束
      if (page === urls.length) {
        return;
      }
      let index = page;
      let url = urls[page];
      page++;
      try {
        // 接口获取
        const resp = await axios.get(url);
        results[index] = resp;  // resp加入到results
      } catch (e) {
        results[index] = err;  // 错误信息加入到results
      } finally {
        // 判断是否所有请求都已完成
        count++;
        if (count === urls.length) {
          resolve(results);
        }
        req();
      }
    };
  // 使用Math.min来避免数组长度小于maxNum。
    for (let i = 0, len = Math.min(maxNum, urls.length); i < len; i++) {
      req();
    }

  });
};

// 执行并发请求
requestConcur(listUrls,10).then(res=>{
  let ids = res.data; // 得到所有id
  // 生成vue文件,可以通过shell脚本创建软连来完成。
  ids.forEach(code=>{
      let template = `${process.cwd()}/pages/article/index.vue`;
      let target = `${process.cwd()}/pages/article/${code}.vue`
      sh.push(`ln -s ${template} ${target} \n`);
  });
  // 创建文件,serverPath是自己指定你的一个目录
  fs.writeFileSync(`${serverPath}/softln.sh`, sh.join(""), {
    encoding: "utf8",
    flags: "a",
    mode: 438
  });
});

得到一个sh脚本文件后,执行:

$ sh 目录/softln.sh

会得到以下信息,并可以开始 npm generate 了。

image.png

在打包之前,我们要先配置好 nuxt.config.ts 文件,我的配置是这样的。

// 详情配置查看:https://nuxt.com/docs/guide/directory-structure/nuxt.config
export default defineNuxtConfig({
    app: {
        // baseURL: "", // 执行generate得注销这句,有影响。
        head: {},
        cdnURL: "http://xxxxx.com/assets/xxx/" // 配置一个绝对地址,指向资源存放目录,类似于webpack中的output.publicPath
    },
    // 官方有介绍这里的配置,不配置则使用官方自定义的配置。
    webpack: {}
});

npm generate的之后,对应pages下的文件目录打包产出:

image.png

打完包的资源,如果用的是vscode软件,可以安装Live Server预览一下是否正常显示。

image.png

会看到预览是正常的,我们打开开发者工具会发现有一个_payload.js文件404。这个文件地址与我们配置cndURL没有关联,官方给的信息也很少,他的作用就是在模板上生成我们请求的数据做静态化。nuxt payload

image.png

引入路径也有问题,因为并不是对应域名的根目录下。

image.png

<link rel="modulepreload" href="/article/15232e7574/_payload.js" />
<script type="module">import p from "/article/15232e7574/_payload.js";</script>

解决他!
建议直接修改打包出来的dist/_nuxt/index.html文件,修正_payload.js的引入。

// 去掉html文件里无效的、重复的style标签和内容
const fs = require("fs");
const path = require("path");
const tools = require("./utils/tools");

// 清理一些数据
function emptyHtmlStyleTag(path) {
  let pwdPath = tools.resolve(path);
  fs.readdir(pwdPath, (err, files) => {
    if (err) {
      throw err;
    }
    files = files.filter(it => !['_nuxt'].includes(it));

    for (let i = 0, len = files.length; i < len; i++) {
      let item = files[i];
      let dirPath = `${pwdPath}/${item}`;
      let stats = fs.statSync(dirPath);

      let isDir = stats.isDirectory();
      let isFile = stats.isFile();
      if (isDir) {
        emptyHtmlStyleTag(dirPath);
      } else if (isFile) {
        let _regext = new RegExp(`(?<=(\\/))(index)+?(\\.html)$`, 'gi');
        let _p = (dirPath.match(_regext) || []);
        if (_p.length) {
          let content = fs.readFileSync(dirPath);
          content = content.toString();
          // _payload.js正确指向
          content = content.replace(/(?<=(href="))[\/\_\.0-9a-zA-Z]+?(_payload\.js)/gi, "./_payload.js");
          content = content.replace(/(?<=(from "))[\/\_\.0-9a-zA-Z]+?(_payload\.js)/gi, "./_payload.js");

          fs.writeFileSync(dirPath, content, {
            encoding: "utf8",
            flags: "a",
            mode: 438
          });
          // console.log(content);
        }
      }
    }
  });
};

emptyHtmlStyleTag("./dist/");

当你修正之后,去预览你的index.html会发现router的问题:

image.png

因为我们使用了<NuxtPage>,启动了路由机制,所以静态页面里会加载这个路由规则。还记得我们生产之后的目录结构吧?

image.png

文件里有个[id]/index.html,这样就跟nuxt的pages约束不一致了(我觉得这是nuxt的一个没考虑到的问题)。

找到 /dist/_nuxt/entry.[hash].js并修正一下。

// entry.[hash].js 
(Qo=y==null?void 0:y.path)!=null?Qo:"/article/0169807e2f/index.html",file:"/Users/path/Desktop/works/nuxt/pages/article/0169807e2f.vue",children:[],meta:y,alias:(y==null?void 0:y.alias)||[]
// 修正代码
function changeFileContent(path) {
  let pwdPath = tools.resolve(path);
  fs.readdir(pwdPath, (err, files) => {
    if (err) {
      throw err;
    }
    files = files.find(item => /(?<=(entry\.))[A-z0-9]+?(?=\.js)/gi.test(item));

    let filepath = `${pwdPath}/${files}`;
    let stats = fs.statSync(filepath);
    let isFile = stats.isFile();
    if (isFile) {
      fs.readFile(filepath, (err, resp) => {
        let text = resp.toString();
        text = text.replace(
          /(?<=\")(\/article)[\/0-9a-zA-Z]+?(?=\")/gi,
          (p => {
            return `${p}/index.html`;
          })
        );

        fs.writeFileSync(filepath, text, {
          encoding: "utf8",
          flags: "a",
          mode: 438
        });
      });
    }
  })
};
changeFileContent("./dist/_nuxt/");

我们再结合nginx,配置一下代理的转发,就可以完成了。

location ^~ /article/ {
    # add_header Content-Type text/html;
    # add_header Content-Disposition inline;
    # https://xxx.com/prod/0169807e2f/index.html
    proxy_pass https://xxx.com/prod/article/;
    access_log off;
}

效率

我们说了很多工作流程,如果一条命令一条命令的执行,必然太繁琐了,不符合人类对效率的追求。所以我们把命令写到一个sh文件中,可以一键执行任务。

# 删除文件夹,省的有缓存。
rm -rf ./dist ./.output/public/

# 获取id,创建软连。
npm run softConnection
# 创建[id].vue
sh services/shell/softlink.sh

# 执行程序:generate
npm run generate --appname=blog --appenv=prod 

# 修复 _payload.js指向
npm run delsty

# 补齐文件
cp -p services/template/vue3/_payload.js dist/_nuxt/_payload.js

# 修改nuxt路由
npm run cfc

# 上传oss
npm run pushoss --appname=blog --appenv=prod

# 删除软连
sh services/shell/delSoftLink.sh

至于什么时候执行这些事务,可以结合自身需求。
如果想在部署环境中执行,建议通过事务触发,或者分出一台小机器,做定时任务跑一下。

伙伴们也可以结合docker使用,还不会docker使用的小伙伴可以看下我这篇文章:docker命令操作

可以看下我的博客效果:传送门;

希望可以帮到遇到类似问题的小伙伴们。

推荐文章

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容