发布于 2026-01-06 2 阅读
0

一个小型项目,从构思到部署

一个小型项目,从构思到部署

刚入行的时候,我一直搞不懂该怎么处理“项目”。很难一眼看出一个项目的范围,以及其中的步骤和注意事项。我渴望了解幕后运作。每个人的工作方式都不一样,但在这篇文章里,我会展示一下,当我灵光一闪想到某个小点子时,我是如何着手做的。一起来看看我是如何解决这个小问题的吧。

我之前在 MDN Web Docs 上寻找一些可以激发我研究灵感的内容,用来撰写本周的文章,结果发现那里没有随机页面链接。如果能添加一个随机页面链接功能,对于复习 Web 技术来说会非常有用,可以让你点击进入那些看起来有趣的内容。

项目主页/唯一页面

我的开发之旅始于对网站首页进行 JavaScript 代码的修改。这通常也是我在工作中解决前端 Web 问题的方式,我会使用开发者工具来创建网站的草稿版本。

查看表格行

HTML 代码中有一个表格主体,位于 `<table>` 标签处document.querySelector("#wikiArticle > table > tbody")。在 Chrome 浏览器中,您可以右键单击元素并选择“Copy -> Copy JS Path查找”来找到它。每个条目使用三行表格。我需要页面链接和摘要信息。让我们将这些行分成几组,每组包含一个条目。

你可以在控制台中运行这些命令。

// Index table
const indexTable = document.querySelector("#wikiArticle > table > tbody");

// There are three <tr>s to a row
const allRows = indexTable.querySelectorAll('tr');
const chunkRows = [];
for (var i = 0; i < allRows.length; i += 3) {
    chunkRows.push([allRows[i], allRows[i + 1], allRows[i + 2]]);
}
Enter fullscreen mode Exit fullscreen mode

我需要将首页数据副本存储在某个地方。每次请求都抓取页面既浪费资源又很慢。考虑到我要通过网络分发这些数据,以 JSON 格式存储是最合理的选择。页面片段应该包含 link 属性和 text 属性。我也会在这里处理一些模板相关的工作。

// Map these chunked rows into a row object
const pageEntries = [];
chunkRows.forEach(row => {
    const a = row[0].querySelector('a');
    const page = {
        link: `<a class="page-link "href="${a.href}">${a.innerText}</a>`,
        text: `<div class="page-text">${row[1].querySelector('td').innerText}</div>`
    }
    pageEntries.push(page);
})
Enter fullscreen mode Exit fullscreen mode

我需要决定如何将这个功能交付给用户。我考虑过使用 Chrome 扩展程序,但我想更快地做出原型,所以我转而使用 Glitch 并使用了他们的 Express 模板。

我将抓取逻辑封装成一个异步函数,并使用Puppeteer在首页上下文中运行这些命令。(在 Glitch 上,你必须使用 `git --no-sandboxadd`,这可能存在安全风险。)在最终项目中,可以手动调用此脚本node getNewPages.js来更新磁盘上的条目。(这非常适合添加到 cron 任务中!)

从 Chrome DevTools 的各种技巧性做法,到更具凝聚力的做法。

// getNewPages.js

const fs = require('fs');
const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
      args: ['--no-sandbox'] // Required for Glitch
    });
    const page = await browser.newPage();
    await page.goto('https://developer.mozilla.org/en-US/docs/Web/JavaScript/Index');

    // Scan index table, gather links and summaries
    const pageEntries = await page.evaluate(() => {

        // Index table
        const indexTable = document.querySelector("#wikiArticle > table > tbody");

        // There are three <tr>s to a row
        const allRows = indexTable.querySelectorAll('tr');
        const chunkRows = [];
        for (var i = 0; i < allRows.length; i += 3) {
            chunkRows.push([allRows[i], allRows[i + 1], allRows[i + 2]]);
        }

        // Map these chunked rows into a row object
        const pageEntries = [];
        chunkRows.forEach(row => {
            const a = row[0].querySelector('a');
            const page = {
                link: `<a class="page-link "href="${a.href}">${a.innerText}</a>`,
                text: `<div class="page-text">${row[1].querySelector('td').innerText}</div>`
            }
            pageEntries.push(page);
        })
        return pageEntries;
    });

    // Save page objects to JSON on disk
    const pageJSON = JSON.stringify(pageEntries);
    fs.writeFile('./data/pages.json', pageJSON, function (err) {
        if (err) {
            return console.log(err);
        }
        console.log(`New pages saved! (JSON length: ${pageJSON.length})`);
    });

    await browser.close();
})();
Enter fullscreen mode Exit fullscreen mode

你可以使用 Node.js 将 JSON 文件加载到 Node 中,这样require('./data/pages.json')就能将所有页面条目保存在内存中,从而加快访问速度(这得益于文件大小固定且很小,约为 300kb)。我们 Web 应用的其余部分是对一个随机函数的封装。

// server.js

// init project
const express = require('express');
const app = express();
app.use(express.static('public'));

const pages = require('./data/pages.json');
const rndPage = () => pages[Math.floor(Math.random() * pages.length)];

app.get('/', (req, res) => res.sendFile(__dirname + '/views/index.html'));

app.get('/rnd', (req, res) => res.send(rndPage()));

// listen for requests :)
const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});
Enter fullscreen mode Exit fullscreen mode

我们只需要一段客户端 JavaScript 代码fetch来更新页面链接和摘要,以及一个带有可爱摇摆动画的按钮来翻阅条目。这段代码有 1/897 的概率出现两次相同的情况,你会如何解决这个问题?请在评论区告诉我!

接下来该怎么做: MDN 文档中还有其他页面(JavaScript 除外)遵循相同的行模式,这意味着只需更改脚本中的 URL 即可抓取它们。

您可以重新混合该项目,以实时玩转代码副本,或者克隆存储库:healeycodes/random-mdn-page


已有超过 150 人订阅了我的编程和个人成长简讯!

我在推特上发布关于科技的内容,账号是@healeycodes

文章来源:https://dev.to/healeycodes/a-tiny-project-from-inception-to-deployment-784