前回の記事では、オープン Web フレームワークである Oats~i についての紹介記事を書きました。その中心的な機能、その仕組み、そして何が期待できるかについてお話しました。この記事をまだ読んでいない場合は、ちょっと読んでみてください。
ここ数日間、フレームワークのインストール、スターター プロジェクトの実行、チュートリアルと学習をシームレスに開始して実行できるように、Oats~i を中心としたいくつかのツールを作成しました。
この作品は、「Build a Web App with Oats~i」というシリーズの最初の作品です。まずは 2 つの方法 (CLI を使用するか手動で) を使用して Oats~i プロジェクトをセットアップします。
これは、Oats~i プロジェクトをセットアップする場合に最も推奨される方法です。これにより、定型コードを作成し、Oats~i プロジェクトを稼働させるために必要な依存関係をインストールする時間を節約できます。
ただし、Oats~i と現在のプロジェクト構造の間でファイルが競合する可能性を避けるために、この方法は完全に新しいプロジェクトを作成する場合にのみ使用してください。
CLI には、ホームと概要ページを備えたスターター プロジェクトがセットアップされています。これら 2 つのページ間を移動すると、Oats~i がすでに動作し、ルーティングとフラグメントを処理していることを確認できます。
npx oats-i-cli
npm run dev
Oats~i をインストールしたい既存のプロジェクトがある場合、または本格的な作業が好きな場合は、Oats~i を手動でセットアップできます。このプロセスは非常に時間がかかり、すべてがうまく機能することを確認するためにより多くの注意が必要です。
次に、プロジェクトのディレクトリに移動してターミナルを開きます。
まず、Oats~i をビルドして実行するために必要な依存関係をインストールします。 新しいプロジェクトを開始する場合は、まず実行してください。
npm init -y
次に、以下の手順に従います。
注: 現在のプロジェクトに既にライブラリ/依存関係がインストールされている場合は、Oats~i のインストール以外の手順を省略できます。
コア Oats~i ライブラリをインストールします。
走る
npm install oats-i
Webpack を開発依存関係としてインストールします。 Webpack を使用すると、ライブラリ処理モジュールのバンドルやアセット管理など、他の機能の中でも特に優れたプロジェクト構造を実現できます。
走る
npm install --save-dev webpack webpack-cli
webpack 開発サーバーを開発依存関係としてインストールします。これにより、Oats~i Web アプリの構築とテスト中に、新しい変更を自動更新する開発サーバーを実行できるようになります。
走る
npm install --save-dev webpack-dev-server
Oats~i でビューをレンダリングするには、テンプレート エンジンを使用することを強くお勧めします。私のお気に入りの選択はハンドルバーです。 (ハンドルバーについて詳しくはこちらをご覧ください)
webpack を使用するには、開発依存関係として handlebars-loader をインストールする必要があります。これにより、ハンドルバー テンプレートを使用してアプリ内でビューを生成およびレンダリングできるようになります。
走る
npm install --save-dev handlebars-loader
サーバー側のビューを作成するために、基本的な Oats~i 構成では html-loader と html-webpack-plugin の組み合わせを使用します。まず、開発依存関係として html-loader ライブラリをインストールしましょう。
走る
npm install --save-dev html-loader
html-webpack-plugin ライブラリを使用すると、webpack を使用してアプリのサーバー側ビューを出力できます。 html-loaderと連携して動作します。開発依存関係としてインストールします。
走る
npm install --save-dev html-webpack-plugin
Babel-loader は、webpack を使用して JavaScript ファイルをロードし、変換します。開発依存関係としてインストールします。
走る
npm install --save-dev babel-loader
Style-loader と css-loader は、html-loader と html-webpack-plugin によって生成された HTML ファイルに、CSS インポートをスタイルシートとして挿入します。これらのローダーを開発依存関係としてインストールします。
走る
npm install --save-dev style-loader npm install --save-dev css-loader
Webpack-merge を使用すると、複数の Webpack 構成ファイルをマージできるため、プロジェクトのセットアップに最適な方法で構成ファイルを構造化できます。このライブラリを開発依存関係としてインストールします。
走る
npm install --save-dev webpack-merge
Express-handlebars will allow us to emulate server-side rendering in development using handlebars view files outputted by our webpack configuration, using html-loader and html-webpack-plugin. Install this library as a development dependency.
Run
npm install --save-dev express-handlebars
At the root of your project’s directory, create a new folder and call it “webpack-configs”.
Navigate into this folder and create two new folders inside it named “main” and “oats~i”.
Your folder structure should now look like this:
Now, navigate into “oats~i” and create two more folders named “default” and “main”.
Your folder structure should now look like this:
------
The “default” folder will hold the default webpack configuration needed by Oats~i to have it’s webpack-dependent functions work. Currently, that is code splitting and lazy loading for fragments.
The “main” folder will hold the webpack configuration for loaders used and recommended by Oats~i. These are the loaders we installed in the “install dependencies” stage. Feel free to edit this configuration later if you want to change loaders.
------
Navigate to the “default” folder and create a new file called “webpack.config.js”
Open the file and paste the following code inside it.
//@ts-check const DefaultOats_iConfig = { optimization: { splitChunks: { minSize: 0, //Minimum size, in bytes, for a chunk to be generated. minSizeReduction: 1, //Minimum size reduction to the main chunk (bundle), in bytes, needed for a chunk to be generated. minChunks: 2, cacheGroups: { commons: { chunks: "async", //Allow chunks to be shared between sync and async } } } } } module.exports = DefaultOats_iConfig;
Now, navigate back to the “oats~i” folder and navigate into “main”.
Create a new file and name it “webpack.config.js”.
Open the file and paste the following code inside.
//@ts-check /** * Contains loaders */ const DefaultOats_iLoadersConfig = { module: { rules: [ { test: /\.(html|sv.hbs|p.hbs)$/, use: [ { loader: "html-loader", options: { minimize: false } } ] }, { test: /\.(hbs)$/, exclude: /\.(sv.hbs|p.hbs)/, use: [ { loader: "handlebars-loader", options: { inlineRequires: "./assets" } } ] }, { test: /\.(js)$/, exclude: /node_modules/, use: [ { loader: "babel-loader" } ] }, { test: /\.(png|svg|jpg|gif)$/, type: 'asset/resource', }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] } } module.exports = DefaultOats_iLoadersConfig;
We’re done setting up the core webpack configuration for Oats~i. Now, we need to merge them in a common configuration file that we’ll use project-wide.
Now, navigate back to the “oats~i” folder then back to the “webpack-configurations” folder. Now navigate into “main”.
Create a new file and name it “webpack.config.js”.
Open the file and paste the following code inside.
//@ts-check const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const DevServerMiddlewareConfig = require("../../proxy-server/proxy_server"); //The folder we'll have our assets emitted after build const DIST_PATH_PUBLIC_ASSETS = "../../dist/public"; const { merge } = require("webpack-merge"); const DefaultOats_iConfig = require("../oats~i/default/webpack.config"); const DefaultOats_iLoadersConfig = require("../oats~i/main/webpack.config"); //@ts-expect-error module.exports = merge(DefaultOats_iConfig, DefaultOats_iLoadersConfig, { mode: "development", devtool: "eval-source-map", output: { //Where we'll output public assets path: path.resolve(__dirname, `${DIST_PATH_PUBLIC_ASSETS}`), publicPath: "/", assetModuleFilename: 'assets/[name][ext]', filename: "js/[name].dev_bundle.js", clean: true }, entry: { //The main entry (app) index: "./src/app/index/scripts/index.js", }, plugins: [ new HtmlWebpackPlugin({ template: "./src/server/home/home.sv.hbs", filename: "../views/home.hbs", chunks: ["index"], minify: false }) ], devServer: { devMiddleware: { writeToDisk: true, //Because of our configured server }, setupMiddlewares: DevServerMiddlewareConfig, } });
Now, we should be done setting up our webpack configurations that’s just fine to run an Oats~i project.
Navigate back to your project’s root folder. Open package.json, look for the “scripts” line, and add the following line after “test” (remember to separate with a comma).
"dev": "webpack-dev-server --config ./webpack-configs/main/webpack.config.js"
In our final webpack configuration file, we specified a middlewares file for the webpack dev server under
setupMiddlewares: DevServerMiddlewareConfig
Under normal circumstances, you don’t need this setup. You can simply write your server view files in html format, use html-loader and html-webpack-plugin to produce them, and have them directly served by webpack-dev-server during development.
However, as you’ll come to learn later, this is not the best setup for building an Oats~i project that’s already primed for server-side rendering. The server-side files are already in html format, meaning they can’t be easily templated with data before being rendered to the client on the initial request.
To accommodate that, the default Oats~i setup ensures you’re creating template files for your server views that will be easy to render with data from your server every time a client requests for a fresh page.
Our dev server middlewares setup will allow us to mimic such as setup on the actual server, for our development environment.
With its default setup, you don’t need to update it for new fragments that you add to the project, as long as you’re not interested in having them server-side rendered. However, once you get to the point where you want to have server-side rendering and test it in development, setting things up will be much easier and faster, without a change in file formats you’ve already used across the project.
At your project’s root directory, create a new folder and name it “proxy-server”. Inside this new folder, create a file and name it “proxy_server.js”
Open the file and paste the following code:
//@ts-check const express = require("express"); const path = require("path"); const hbs = require("express-handlebars"); const DevServerMiddlewareConfig = (middlewares, devServer) => { /** * @type {import("express").Application} */ const app = devServer.app; //Configure the view engine app.set("views", path.resolve(__dirname, "../dist/views")); app.set("view engine", "hbs"); app.engine("hbs", hbs.engine({ extname: "hbs", layoutsDir: path.resolve(__dirname, "../dist/views"), partialsDir: path.resolve(__dirname, "../dist/views/partials") })); //for json app.use(express.json()); //I think params and queries app.use(express.urlencoded({ extended: false })); //static app.use(express.static(path.resolve(__dirname, "../dist/public"))); //My middlewares //Capture all app.get("/*", (req, res, next) => { res.render("home", { layout: "home" }); }); return middlewares; } module.exports = DevServerMiddlewareConfig;
This configuration will capture all requests to the dev server and return the home.hbs layout file. You can rename this later to your file’s actual name once you start creating your own Oats~i project and leave it as is as long as you’ll not require server-side rendering for any of your fragments.
Oats~i is typed using a combination of typescript declaration files and JSDoc. There’s a slight issue where types may not always reflect correctly when using the framework, slightly hurting the developer experience.
Instead of refactoring over 100 files and thousands of lines of code, I’ve found a way to make typescript and intellisense (at least in VSCode) to understand the JSDoc types used in Oats~i.
To do this, navigate to your project’s root folder. Create a file named “jsconfig.json”.
Open it and paste the code below:
{ "include": [ "*/**/*.js", "**/*", "/**/*", "node_modules/oats-i" //to get type definitions for oats-i in your project ], }
NOTE: This bit comes automatically with the cli, so don’t do this for an Oats~i project you’ve set up using the cli.
Let’s now put everything together and create our starter project files to run an Oats~i app for the first time.
Navigate to your project’s root folder and create a new folder named “src”. This folder will contain all of our project’s source files.
Inside the “src” folder, create two folders named “app” and “server”.
Navigate to the “server” folder and create a new folder named “home”. Inside the “home” folder, create a new file and name it “home.sv.hbs”
Open the file and paste the code below:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Home - My Oats~i App</title> </head> <body> <app-root id="my-app"> <div id="nav"> <a href="/" class="home-link">Home</a> </div> <main-fragment> </main-fragment> </app-root> </body> </html>
Now navigate back to “src”. Get into the “app” folder and create two folders name “fragments” and “index”.
Navigate into the “index” folder and create two folders named “scripts” and “styles”.
Inside the “scripts” folder, create a new folder called “routing-info”. Inside “routing-info” create two files named “app_main_nav_info.js” and “app_routing_info.js”
Open “app_main_nav_info.js” and paste the following code:
//@ts-check import MainNavigationInfoBuilder from "oats-i/router/utils/nav-info/main_nav_info_builder"; const AppMainNavInfo = MainNavigationInfoBuilder.buildMainNavigationInfo([ { selector: "home-link", defaultRoute: "/", baseActiveRoute: "/", } ]); export default AppMainNavInfo;
Now open “app_routing_info.js” and paste the following code:
//@ts-check import RoutingInfoUtils from "oats-i/router/utils/routing-info/routing_info_utils"; import AppMainNavInfo from "./app_main_nav_info"; import homeMainFragmentBuilder from "../../../fragments/home/scripts/home_main_fragment"; const AppRoutingInfo = RoutingInfoUtils.buildMainRoutingInfo([ { route: "/", target: homeMainFragmentBuilder, nestedChildFragments: null } ], AppMainNavInfo); export default AppRoutingInfo;
We’ll create an index.css file for a special reason, which MUST be replicated across all your Oats~i projects if you want consistent behavior.
Navigate back to the “index” folder, and create a new folder named “styles”. Inside the folder, create a new file called “index.css”
Open the file and paste the following code:
/* Crucial styling to allow specially structured A links to still have clicks intercepted by router. */ /* Carry over to your project */ a *:not([click-override=true]){ pointer-events: none }
What this css code does is remove pointer events from elements nested inside an A tag, to ensure the browser doesn’t intercept it before Oats~i does. It also gives you, the developer, the freedom to override this behavior using the attribute click-override=true on any element nested within an A tag.
However, expect Oats~i, at its current state, not to intercept links from an A tag with a child element having that attribute.
This means that you can safely write A tags without any modification or special attributes for Oats~i to automatically intercept them and navigate your app locally. You only add special attributes when you want to stop this behavior and have the browser manually route the website.
Carry over this css directive in all Oats~i projects you create. If you use the cli, you’ll find it already in index.css.
Navigate back to “scripts” (inside index) and create a new file named “index.js”.
Open the file and paste the following code.
//@ts-check //import styles import "../styles/index.css"; import AppStateManager from "oats-i/base-history/app_state_manager"; import appRoot from "oats-i/bin/app_root" import AppRoutingInfo from "./routing-info/app_routing_info"; import MainRouter from "oats-i/router/main_router"; import AppMainNavInfo from "./routing-info/app_main_nav_info"; function initApp(){ const appStateManager = new AppStateManager(AppRoutingInfo); appRoot.initApp(appStateManager, new MainRouter(AppRoutingInfo, appStateManager, (args) => {}, "", async (url) => { return { canAccess: true, fallbackRoute: "/" } }), { template: null, mainNavInfos: AppMainNavInfo }, ""); } initApp();
Navigate back to the “app” folder. Navigate into “fragments” and create a new folder named “home”.
Inside “home”, create a new folder named “scripts”. Inside “scripts”, create a new file named “home_main_fragment.js”.
Open the file and paste the code below.
//@ts-check import AppFragmentBuilder from "oats-i/fragments/builder/AppFragmentBuilder"; import AppMainFragment from "oats-i/fragments/AppMainFragment" class HomeMainFragment extends AppMainFragment{ async initializeView(cb){ //@ts-expect-error cannot find module (for view) const uiTemplate = require("../views/home_fragment.hbs")(); this.onViewInitSuccess(uiTemplate, cb); } } const homeMainFragmentBuilder = new AppFragmentBuilder(HomeMainFragment, { localRoutingInfos: null, viewID: "home-main-fragment", }); export default homeMainFragmentBuilder;
Now navigate back to “home” and create a new folder called “views”. Inside “views”, create a new file and name it “home_fragment.hbs”
Open file and paste the following code:
<h1>Home Fragment<h1/>
Navigate to your project’s root. Open the terminal and run
npm run dev
This will start the webpack-dev-server which will bundle the files and run Oats~i. If you open the browser at the url shown in the terminal (often is localhost:8080) and see a page with “Home Fragment” showing, your project has been successfully set up and Oats~i is working fine.
Regardless of whether you’ve manually set up an Oats~i project or used the cli, there are configuration flexibilities you can enjoy thanks to Oats~i running on top of Webpack.
Basically, apart from the default Oats~i webpack configuration, you can change anything else to your liking as long as you understand webpack, plugins, and loaders, and how they’ll affect your project.
For instance, you can have a production configuration that will use MiniCssExtractPlugin to extract your css into files that will be added to the final html-webpack-plugin output. You can use advanced babel configurations and even switch handlebars-loader for a loader that suits your favorite templating engine.
However, the default setup provided by Oats~i is good enough for most projects. Later on in the tutorials, we’ll add a new configuration to create the final production build with key features such as minification.
I encourage you to learn about Webpack, why it’s needed, and how you can configure it, to make the most out of Oats~i and other projects you may have using Webpack as a bundler.
That’s it for setting up Oats~i for your project. If you’re working on a new project, just use the cli. It’s easier, faster, and will directly load you into a beautiful starter project that you can inspect and start getting ideas of how to setup a full project with view, styling, and script files in Oats~I, before we start doing that together.
In the next tutorial, we’ll create our first simple project in Oats~i, where we’ll start learning what routing infos, nav infos, and fragments are in Oats~i.
Leave a like and follow to get notified when the next tutorial drops.
See you then.
Support Oats~i
You can support the development of Oats~i through Patreon or buy me a coffee.
以上がOats~i を使用して Web アプリを構築する – セットアップの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。