[Combat] webpack4 + ejs + express перенесет вас в многостраничную архитектуру проекта приложения

Webpack

окончательное решение

Выпущено окончательное решение архитектуры проекта многостраничного приложения~



[COUNT] Окончательное решение WebPack4 + EJS + Multi-Page Multi-Page Project Project

eggВерсия для обновления производится в этом выпуске на основе надежности и масштабируемости сервера - увеличилась, но большинство деталей этого были полностью представлены, мы можем с нетерпением ждать двух совмещенных вместе.

предисловие

Полный адрес проекта на GitHub

Недавно я взял на себя проект на официальном веб-сайте компании, который требует SEO-дружественности, поэтому я не могу использовать интерфейсный фреймворк, а вспомогательные инструменты, которые поставляются с интерфейсным фреймворком, естественно, не могут помочь. просто используйте его самиwebpack4 + ejs + express, построить архитектуру проекта многостраничного приложения с нуля. В процессе строительства я столкнулся со многими ямами, но в Интернете очень мало упоминаний о них, поэтому я написал блог, чтобы записывать процесс строительства и меры предосторожности.

Ниже я отмечу важные детали красным для справки друзьям, которым это нужно.

«Изоморфный» или «рендеринг на стороне сервера»?

После того, как эта статья была опубликована, друг спросил в области комментариев, почему бы напрямую не использовать некоторые изоморфные фреймворки, такие какnextjsилиnuxtjs? Эта проблема также может быть одной из самых запутанных проблем, когда мы развиваемся, позвольте мне рассказать о моих собственных мыслях.

изоморфизм

По сути, так называемая «изоморфизм", только когда загружается первый экран веб-страницы, используется рендеринг на стороне сервера, и сервер анализирует VDOM для создания реального DOM, а затем возвращается. Когда загружается код первого экрана веб-страницы и внешний интерфейс framework берет на себя браузер, весь последующий процесс будет завершен, это уже рендеринг на стороне клиента, и к серверу он не имеет никакого отношения.

преимущество

  • Благодаря интерфейсной структуре структура страницы может быть организована в виде «компонентов», что является более гибким и пригодным для повторного использования.
  • Поддерживаемые тремя большими интерфейсными фреймворками, различные подключаемые компоненты имеют хорошую экологию.
  • Последующее переключение страниц происходит плавно, а взаимодействие с пользователем хорошее.
  • Удобная работа с данными и обмен данными, подходящий для сложных бизнес-сценариев или сценариев, требующих обмена данными

недостаток

  • Время загрузки первого экрана медленное, время белого экрана долгое, и будет экран-заставка (фактически из-за кода на стороне клиента, выполняющего другой рендеринг на стороне клиента)
  • При парсинге VDOM и построении DOM сервером есть определенные потери в производительности, что в случае большого количества посещений может стать узким местом в производительности сервера.
  • Процесс строительства сложен, в процессе строительства возникают проблемы, а устранение неполадок затруднено.
  • Синхронизация данных затруднена из-за обезвоживания данных и закачки воды.

рендеринг на стороне сервера

В традиционном смысле рендеринга на стороне сервера серверная сторона напрямую генерирует статическую страницу и возвращает ее клиенту в течение всего жизненного цикла веб-страницы.

преимущество

  • Процесс строительства относительно прост, а место ошибки удобно.
  • Накладные расходы на производительность на стороне сервера невелики
  • Первый экран загружается быстро, а время белого экрана короткое

недостаток

  • Передняя и задняя части сильно связаны, код недостаточно гибкий, и его сложно использовать повторно.
  • Операции с данными сложны, требуют от разработчиков ручного вмешательства в сложные операции DOM, не подходят для интенсивного взаимодействия и крупномасштабных операций с данными.
  • В интерфейсном коде отсутствуют ограничения и управление фреймворком, что упрощает написание исходного кода.

выбор

На основе вышеизложенного, когда мы столкнулись с необходимостью рассмотретьSEOпроекта, когда выбрать изоморфизм внешнего и внутреннего интерфейса, а когда выбрать традиционный рендеринг на стороне сервера?

Я думаю: если ваш проектtoCпродукты, которые необходимо учитыватьSEO, предполагающий большое количество взаимодействий с пользователем и частые изменения требований, то изоморфизм может подойти вам больше. Он может построить ваш проект в виде компонентов, которые сильно абстрагируются и повторно используются, и может поддерживать некоторые функции, которые невозможно выполнить в случае традиционного рендеринга на стороне сервера, например, клиент веб-страницы с облачной музыкой веб-страницы, а также может использоваться при переключении страниц.Чтобы песня была непрерывной, обязательно используется изоморфизм.

И если только одинtoBОфициальный веб-сайт малого и среднего предприятия учитывает SEO, но не требует большого взаимодействия с пользователем.После того, как изменения постобработки невелики, вы можете рассмотреть возможность выбора традиционного рендеринга на стороне сервера, который является методом, упомянутым в следующая статья.

четкие потребности

Перед началом разработки нам необходимо уточнить позиционирование данного проекта -официальный сайт компании, Вообще говоря, официальный сайт не предполагает большого количества взаимодействия с данными и больше склонен к отображению данных. Таким образом, интерфейсная структура не требуется,jqueryдля удовлетворения потребностей. Но с учетом SEO, поэтому вам нужно использовать рендеринг на стороне сервера, вам нужно использовать язык шаблонов (ejs), в комплекте с узлом.

Основываясь на приведенной выше информации, мы можем определить основные функции скрипта упаковки.Сначала составим простой список:

  1. нужноwebpackдля упаковки многостраничных приложений без добавления каждый раз нового файла представленияHTMLWebpackPluginИ перезапуск сервера может отделить конфигурацию веб-пакета и имя файла и максимально автоматизировать.
  2. Нужно использоватьejsНаписан на шаблонном языке с возможностью вставки переменных и внешнихincludesфайл, общий файл шаблона (<meta>/<title>/<header>/<footer>и т. д.) автоматически вставляются в соответствующее место каждого файла представления.
  3. Требуется рендеринг на стороне сервера, поэтому среда разработки должна быть отделена от интеграции с веб-пакетом.webpack-dev-server, вы можете использовать свой собственный код узла для запуска службы.
  4. иметь идеальныйoverlayфункция, может быть какwebpack-dev-serverЭто объединяет хороший оверлейный экран для сообщения об ошибках.
  5. Может отслеживать изменения файлов, автоматически упаковывать и перезапускать службы, предпочтительно горячее обновление

начать строить

Сначала создайте пустой проект, так как нам нужно самим писать код сервера, нам нужно построить еще один/serverпапка для храненияexpressКод, после завершения сборки, структура нашего проекта выглядит так.

В дополнение к этому нам необходимо инициализировать некоторые общие файлы конфигурации, в том числе:

  • .babelrcконфигурационный файл бабеля
  • .gitignoregit игнорировать файлы
  • .editorConfigфайл конфигурации редактора
  • .eslintrc.jsконфигурационный файл eslint
  • README.mdдокумент
  • package.jsonдокумент

После того, как вышел большой фреймворк, мы начали писать инженерный код.

сценарий упаковки

Во-первых, написать сценарий упаковки, в/buildСоздайте несколько новых файлов в папке

  1. webpack.base.config.jsИспользуется для хранения общих конфигураций WebPack для среды производства и развития
  2. webpack.dev.config.jsСреда разработки упаковки, используемая для хранения конфигурации
  3. webpack.prod.config.jsКонфигурация упаковки, используемая для хранения производственной среды
  4. config.jsonИспользуется для хранения некоторых констант конфигурации, таких как имя порта, имя пути и т. д.

Вообще говоря,webpack.base.configВ файл поместите некоторые общие конфигурации для среды разработки и производства, такие какoutput,entryи немногоloader, такие как компиляция синтаксиса ES6babel-loader, упакованные файлыfile-loaderЖдать. Часто используемое использование загрузчика, мы можем просмотреть документациюwebpack loaders,

Следует отметить, что здесь есть очень важный загрузчик — ejs-html-loader

Как правило, мы используемhtml-loaderиди прямо.htmlФайл представления в конце обрабатывается, а затем выбрасывается вhtml-webpack-pluginсоздает соответствующий файл, ноhtml-loaderне может обрабатывать синтаксис шаблона ejs в<% include ... %>синтаксис, сообщит об ошибке. Однако в многостраничных приложениях эта функция включения необходима, иначе каждый файл просмотра придется писать вручную.header/footerкакое ощущение. . . Итак, нам нужно настроить еще один ejs-html-loader:

// webpack.base.config.js 部分代码
module: {
    rules: [
        ...
        {
            test: /\.ejs$/,
            use: [
                {
                    loader: 'html-loader', // 使用 html-loader 处理图片资源的引用
                    options: {
                        attrs: ['img:src', 'img:data-src']
                    }
                },
                {
                    loader: 'ejs-html-loader', // 使用 ejs-html-loader 处理 .ejs 文件的 includes 语法
                    options: {
                        production: process.env.ENV === 'production'
                    }
                }
            ]
        }
        ...
    ]
}

После обхода первой ямы вторая:

Как написать входную запись?

Я помню старый проект предыдущей компании, более 50 страниц, более 50entryа такжеnew HTMLwebpackPlugin()Файл расширяется, чтобы окружить земной шар. . . Чтобы избежать этой трагической ситуации, напишите метод, который возвращает массив записей.

можно использоватьglobЧтобы обработать эти файлы, получите имя файла, конечно, это также можно реализовать с помощью собственного узла. Просто гарантияJavaScriptИмя файла и имя файла представления совпадают, например, имя файла представления домашней страницыhome.ejs, то соответствующее имя файла сценария должно использовать то же имяhome.jsНазвание, WebPack найдет запись файла сценариев при упаковке, и генерировать соответствующий файл просмотра через отношения сопоставления:

// webpack.base.config.js 部分代码
const Webpack = require('Webpack')
const glob = require('glob')
const { resolve } = require('path')

// webpack 入口文件
const entry = ((filepathList) => {
    let entry = {}
    filepathList.forEach(filepath => {
        const list = filepath.split(/[\/|\/\/|\\|\\\\]/g) // 斜杠分割文件目录
        const key = list[list.length - 1].replace(/\.js/g, '') // 拿到文件的 filename
        // 如果是开发环境,才需要引入 hot module
        entry[key] = process.env.NODE_ENV === 'development' ? [filepath, 'webpack-hot-middleware/client?reload=true'] : filepath
    })
    return entry
})(glob.sync(resolve(__dirname, '../src/js/*.js')))

module.exports = {
    entry,
    ...
}

Конфигурация HTMLWebPackPlugin такой же:

// webpack.base.config.js 部分代码
...
plugins: [
    // 打包文件
    ...glob.sync(resolve(__dirname, '../src/tpls/*.ejs')).map((filepath, i) => {
        const tempList = filepath.split(/[\/|\/\/|\\|\\\\]/g)           // 斜杠分割文件目录
        const filename = `views/${tempList[tempList.length - 1]}`       // 拿到文件的 filename
        const template = filepath                                       // 指定模板地址为对应的 ejs 视图文件路径
        const fileChunk = filename.split('.')[0].split(/[\/|\/\/|\\|\\\\]/g).pop() // 获取到对应视图文件的 chunkname
        const chunks = ['manifest', 'vendors', fileChunk]               // 组装 chunks 数组
        return new HtmlWebpackPlugin({ filename, template, chunks })    // 返回 HtmlWebpackPlugin 实例
    })
]
...

хорошо написанwebpack.base.config.jsДокументы, подготовленные в соответствии с потребностями вашего проектаwebpack.dev.config.jsа такжеwebpack.prod.config.js,использоватьwebpack-mergeОбъедините базовую конфигурацию с конфигурацией в соответствующей среде.

Вы можете обратиться к некоторым другим подробным настройкам webpack.URL-адрес веб-пакета на китайском языке

Сервер

Скрипт упаковки написан, начинаем писать сервис, пользуемсяexpressдля создания службы.(Поскольку это демонстрация инженерной архитектуры, эта услуга не включает никаких добавлений, удалений, изменений и проверок базы данных, а включает только базовые переходы маршрутизации)

serverПростая структура выглядит следующим образом:

Файл запуска сервера

bin/server.jswebpack-dev-serverpackage.jsonnpm run devwebpack-dev-serverexpress + webpack-dev-middleware

Ниже приведена часть кода файла служебной записи.

// server/bin/server.js 文件代码
const path = require('path')
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const { routerFactory } = require('../routes')
const isDev = process.env.NODE_ENV === 'development'
let app = express()
let webpackConfig = require('../../build/webpack.dev.config')
let compiler = webpack(webpackConfig)

// 开发环境下才需要启用实时编译和热更新
if (isDev) {
    // 用 webpack-dev-middleware 启动 webpack 编译
    app.use(webpackDevMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        overlay: true,
        hot: true
    }))
    
    // 使用 webpack-hot-middleware 支持热更新
    app.use(webpackHotMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        noInfo: true
    }))
}

// 添加静态资源拦截转发
app.use(webpackConfig.output.publicPath, express.static(path.resolve(__dirname, isDev ? '../../src' : '../../dist')))

// 构造路由
routerFactory(app)

// 错误处理
app.use((err, req, res, next) => {
    res.status(err.status || 500)
    res.send(err.stack || 'Service Error')
})

app.listen(port, () => console.log(`development is listening on port 8888`))

маршрутизация на стороне сервера

Прыжковый метод разводки — очень важный шаг во всем проекте. Не знаю, есть ли сомнения у друзей, прочитавших статью.Локальный файл представления - это файл с суффиксом .ejs, а браузер может распознать только файл с суффиксом .html.Как осуществляется рендеринг данных этого представления ? Все ресурсы, упакованные webpack-dev-middleware, хранятся в памяти Как получить файлы ресурсов, хранящиеся в памяти на сервере?

Давайте сначала посмотрим на конкретный код маршрутизации, здесь для демонстрации используется маршрутизация домашней страницы.
// server/routs/home.js 文件
const ejs = require('ejs')
const { getTemplate } = require('../common/utils')

const homeRoute = function (app) {
    app.get('/', async (req, res, next) => {
        try {
            const template = await getTemplate('index.ejs') // 获取 ejs 模板文件
            let html = ejs.render(template, { title: '首页' })
            res.send(html)
        } catch (e) {
            next(e)
        }
    })
    app.get('/home', async (req, res, next) => {
        try {
            const template = await getTemplate('index.ejs') // 获取 ejs 模板文件
            let html = ejs.render(template, { title: '首页' })
            res.send(html)
        } catch (e) {
            next(e)
        }
    })
}

module.exports = homeRoute

Вы можете видеть, что ключевой момент находится в методе getTemplate, давайте посмотрим на этоgetTemplateчто ты сделал

// server/common/utils.js 文件
const axios = require('axios')
const CONFIG = require('../../build/config')

function getTemplate (filename) {
    return new Promise((resolve, reject) => {
        axios.get(`http://localhost:8888/public/views/${filename}`) // 注意这个 'public' 公共资源前缀非常重要
        .then(res => {
            resolve(res.data)
        })
        .catch(reject)
    })
}

module.exports = {
    getTemplate
}

Как видно из приведенного выше кода, самое важное в маршрутизации — это прямое использование имени файла ejs соответствующего представления для запроса его собственной службы, чтобы получить ресурсы и данные, хранящиеся в кеше веб-пакета.
Получив таким образом строку шаблона, движок ejs отрендерит соответствующую переменную с данными и, наконец, вернет ее в браузер для отрисовки в виде html-строки.
Локальная служба пометит запрос статического ресурса префиксом пути publicPath. Если запрос, полученный службой, имеет префикс publicPath, он будет перехвачен промежуточным программным обеспечением статического ресурса в `/bin/server.js` и сопоставлен с соответствующий каталог ресурсов, вернуть статические ресурсы, и этот publicPath находится в конфигурации веб-пакетаoutput.publicPath

Что касается кэширования веб-пакета при упаковке, я много где искал и не нашел хорошей документации и инструментов для работы.Вот две ссылки для вас, чтобы порекомендовать

  1. Webpack Custom File Systems(Официальное описание пользовательской файловой системы webpack)
  2. memory-fs(чтобы получить данные, скомпилированные в память с помощью веб-пакета)

клиент

После завершения рендеринга на стороне сервера, создания и настройки веб-пакета 80% рабочей нагрузки выполнено, и есть некоторые мелкие детали, на которые необходимо обратить внимание, иначе сервис все равно будет сообщать об ошибке при запуске.

Яма при компиляции веб-пакета

Эта яма зарыта в файле представления клиента. Давайте сначала посмотрим, что это за яма: когда мы используем синтаксис ejs (<%= title %>) Когда используется этот синтаксис, компиляция веб-пакета сообщит об ошибке, говоря, что заголовок не определен

Чтобы решить эту проблему, вам нужно сначала понять механизм времени компиляции веб-пакета, что он делает. Мы знаем, что внутренний шаблонный механизм веб-пакета основан на ejs, поэтому перед нашим рендерингом на стороне сервера, то есть стадией компиляции веб-пакета, один раз был выполнен ejs.render, в это время в файле конфигурации веб-пакета у нас нет Переменная title была передана, поэтому компиляция сообщит об ошибке. Итак, как написать, чтобы идентифицировать его? ответОфициальная документация ejs

Как видно из вступления на официальном сайте, когда мы используем<%%В начале он будет экранирован как<%Строка, аналогичная экранированию тегов html, чтобы избежать ошибочной идентификации ejs, поставляемого с веб-пакетом, и генерировать правильные файлы ejs. Итак, взяв в качестве примера переменные, в коде нам нужно написать:<%%= title %>
Таким образом, вебпак может быть успешно скомпилирован, а компилятор будет продолжать передаваться в ejs-html-loader здесь

Используйте html-загрузчик для идентификации ресурсов изображения

если ты понимаешьhtml-loaderДрузья знают, что в проекте причина, почему мы можем легко писать в html<img src="../static/imgs/XXX.png">Этот формат изображения также может корректно распознаваться вебпаком, который неотделим от html-загрузчика вattrsэлемент конфигурации, Но в ejs-html-loader такой удобной функции нет, поэтому все равно приходится пользоватьсяhtml-loaderДля обработки ссылок на изображения в html необходимо обратить внимание на порядок настройки загрузчика.

// webpack.base.config.js 部分代码
module: {
    rules: [
        ...
        {
            test: /\.ejs$/,
            use: [
                {
                    loader: 'html-loader', // 使用 html-loader 处理图片资源的引用
                    options: {
                        attrs: ['img:src', 'img:data-src']
                    }
                },
                {
                    loader: 'ejs-html-loader', // 使用 ejs-html-loader 处理 .ejs 文件的 includes 语法
                    options: {
                        production: process.env.ENV === 'production'
                    }
                }
            ]
        }
        ...
    ]
}

Настроить горячее обновление

Следующим шагом является настройка горячего обновления, используйтеwebpack-dev-middlewareспособ настройки горячего обновления иwebpack-dev-serverнемного отличается, ноwebpack-dev-middlewareЧуть проще. Горячее обновление конфигурации многостраничного приложения Webpack, всего четыре шага:

  1. существуетentryНапишите еще один в записиwebpack-hot-middleware/client?reload=trueвходной файл
// webpack.base.config.js 部分代码
// webpack 入口文件
const entry = ((filepathList) => {
    let entry = {}
    filepathList.forEach(filepath => {
        ...
        // 如果是开发环境,才需要引入 hot module
        entry[key] = process.env.NODE_ENV === 'development' ? [filepath, 'webpack-hot-middleware/client?reload=true'] : filepath
        ...
    })
    return entry
})(...)

module.exports = {
    entry,
    ...
}
  1. в вебпакеpluginsНапишите еще три плагина:
    // webpack.dev.config.js 文件部分代码
    plugins: [
    ...
    
    // OccurrenceOrderPlugin is needed for webpack 1.x only
    new Webpack.optimize.OccurrenceOrderPlugin(),
    new Webpack.HotModuleReplacementPlugin(),
    // Use NoErrorsPlugin for webpack 1.x
    new Webpack.NoEmitOnErrorsPlugin()
    
    ...
    ]
    
  2. существуетbin/server.jsПредставлено в служебной записиwebpack-hot-middleware, и воляwebpack-dev-serverупакованныйcompilerиспользоватьwebpack-hot-middlewareУпакуйте это:
    // server/bin/server.js 文件
    let compiler = webpack(webpackConfig)
    
    // 用 webpack-dev-middleware 启动 webpack 编译
    app.use(webpackDevMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        overlay: true,
        hot: true
    }))
    
    // 使用 webpack-hot-middleware 支持热更新
    app.use(webpackHotMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        reload: true,
        noInfo: true
    }))
    
  3. Добавьте в файл js фрагмент кода, соответствующий представлению:
    // src/js/index.js 文件
    if (module.hot) {
        module.hot.accept()
    }
    

Дополнительные сведения о конфигурации webpack-hot-middleware см.Документация

Здесь следует отметить, что:
1. Если так написано, то горячий модуль вебпака может поддерживать только модификацию JS части.Если вам нужна поддержка горячей перезагрузки файлов стилей (css/less/sass...), вы не можете использовать extract-text -webpack-плагин для конвертации стиля Файл зачищен, иначе не будет возможности следить за модификациями и обновляться в реальном времени.

2. Горячий модуль webpack нативно не поддерживает горячую замену html, но у многих разработчиков есть большой спрос на это, поэтому я нашел относительно простой способ поддержки горячего обновления файлов просмотра

Вам нужно внести небольшие изменения в исходный код, сначала посмотрите на код:
// src/js/index.js 文件
import axios from 'axios'
// styles
import 'less/index.less'

const isDev = process.env.NODE_ENV === 'development'

// 在开发环境下,使用 raw-loader 引入 ejs 模板文件,强制 webpack 将其视为需要热更新的一部分 bundle
if (isDev) {
    require('raw-loader!../tpls/index.ejs')
}

...

if (module.hot) {
    module.hot.accept()
    /**
    * 监听 hot module 完成事件,重新从服务端获取模板,替换掉原来的 document
    * 这种热更新方式需要注意:
    * 1. 如果你在元素上之前绑定了事件,那么热更新之后,这些事件可能会失效
    * 2. 如果事件在模块卸载之前未销毁,可能会导致内存泄漏
    */
    module.hot.dispose(() => {
        const href = window.location.href
        axios.get(href).then(res => {
            const template = res.data
            document.body.innerHTML = template
        }).catch(e => {
            console.error(e)
        })
    })
}

// webpack.dev.config.js
plugins: [
    ...
    new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('development')
    })
    ...
]
// webpack.prod.config.js
plugins: [
    ...
    new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
    })
    ...
]

Хорошо, как хотите, теперь просмотр файлов также поддерживает горячее обновление. 😃😃

webpack-hot-middlewareнаследуется по умолчаниюoverlay, поэтому, когда конфигурация горячего обновления будет завершена,overlayФункцию сообщения об ошибках также можно использовать в обычном режиме.

Сценарий запуска package.json

Последний взглядpackage.jsonСценарий запуска здесь, здесь нет никаких сложностей, просто перейдите непосредственно к коду

    "scripts": {
        "clear": "rimraf dist",
        "server": "cross-env NODE_ENV=production node ./server/bin/server.js",
        "dev": "cross-env NODE_ENV=development nodemon --watch server ./server/bin/server.js",
        "build": "npm run clear && cross-env NODE_ENV=production webpack --env production --config ./build/webpack.prod.config.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    }

Когда клиентский кодовой код изменяется, WEBPack автоматически поможет нам компилировать и перезапустить, но изменения кода бокового сервера не будут обновлены в режиме реального времени. В настоящее время нам нужно использоватьnodemon, Настроить после каталога прослушивания, любые изменения кода на стороне сервера смогут бытьnodemonМониторинг, сервис перезапускается автоматически, что очень удобно.

Здесь также есть небольшая деталь, на которую стоит обратить внимание, nodemon --watch лучше всего указывать папку сервера мониторинга, ведь ведь для перезапуска службы требуется только модификация кода сервера, иначе вся корневая директория контролируется по умолчанию, а сервис можно перезапустить, написав стиль, чтобы надоедать людям до смерти.

Суммировать

Оглядываясь назад на проект в целом, можно сказать, что есть еще много вещей, которые требуют внимания и заслуживают изучения. Несмотря на то, что я ступил на множество ям, у меня также есть более глубокое понимание некоторых принципов.

Благодаря интерфейсному инструменту строительных лесов мы можем сгенерировать базовую конфигурацию проекта одним щелчком мыши в большинстве проектов, избавляя от многих проблем инженерного строительства, но это удобство не только приносит пользу разработчикам, но и ослабляет фронт- конечные инженеры Возможности инженерной архитектуры. На самом деле всегда есть некоторые бизнес-сценарии, которые не могут быть реализованы с помощью инструментов поддержки.В настоящее время разработчикам необходимо активно искать решения или даже создавать проекты самостоятельно, чтобы получить максимальную гибкость для разработки.

Полный адрес проекта можно посмотреть в моемGitHub, дайте звезду⭐️, если вам понравилось, большое спасибо~😃😃