前头·见·nee·呃
名词:台口
- 剧院舞台幕布前的部分。
Proscenium将您的前端和客户端代码视为 Rails 应用程序的一等公民,并假定“默认情况下速度很快”的互联网。它实时、按需且零配置地捆绑和缩小 JavaScript (+ JSX)、TypeScript (+TSX) 和 CSS。
亮点:
rails s
即可!显然,入门取决于您是将 Proscenium 添加到现有 Rails 应用程序还是创建新应用程序。因此,请选择以下适当的指南:
将此行添加到 Rails 应用程序的 Gemfile 中,然后就可以开始了:
gem 'proscenium'
请注意,Proscenium 专为与 Rails 一起使用而设计。
现在,如果您启动 Rails 应用程序,您可以打开任何前端代码(JS、CSS 等)。例如,可以通过https://localhost:3000/app/assets/stylesheets/application.css
访问app/assets/stylesheets/application.css
assets/stylesheets/application.css 中的文件,该文件将被捆绑、转换和缩小[在生产中]实时。
Proscenium 相信您的前端代码与后端代码一样重要,而不是事后的想法 - 它们应该是您的 Rails 应用程序的一等公民。因此,不必将所有 JS 和 CSS 放入“app/assets”目录中,然后需要单独的进程来编译或捆绑,只需将它们放在应用程序中您想要的任何位置,然后运行 Rails!
例如,如果您的app/views/users/index.html.erb
视图需要一些 JS,只需在app/views/users/index.js
旁边创建一个 JS 文件即可。或者,如果您的整个应用程序使用了一些 CSS,请将其放入app/views/layouts/application.css
中并将其与布局一起加载。也许你有一些 JS 实用函数,所以将它们放在lib/utils.js
中。
只需将 JS(X) 和 CSS 放在您想要的任何位置,Rails 应用程序就会从您放置它们的位置提供服务。
使用上面的例子...
app/views/users/index.js
=> https://localhost:3000/app/views/users/index.js
app/views/layouts/application.css
=> https://localhost:3000/app/views/layouts/application.css
lib/utils.js
=> https://localhost:3000/lib/utils.js
app/components/menu_component.jsx
=> https://localhost:3000/app/components/menu_component.jsx
config/properties.css
=> https://localhost:3000/config/properties.css
当您的资源自动侧面加载时,Proscenium 的体验最佳。
使用 Rails,您通常会使用javascript_include_tag
和stylesheet_link_tag
帮助器以声明方式加载 JavaScript 和 CSS 资源。
例如,您的顶级“应用程序”CSS 可能位于/app/assets/stylesheets/application.css
的文件中。同样,您可能有一些全局 JavaScript 位于/app/javascript/application.js
的文件中。
您可以手动并以声明方式将这两个文件包含在应用程序布局中,如下所示:
<%# /app/views/layouts/application.html.erb %>
<!DOCTYPE html >
< html >
< head >
< title > Hello World </ title >
<%= stylesheet_link_tag 'application' %> <!-- << Your app CSS -->
</ head >
< body >
<%= yield %>
<%= javascript_include_tag 'application' %> <!-- << Your app JS -->
</ body >
</ html >
现在,您可能有一些仅特定视图和部分所需的 CSS 和 JavaScript,因此您可以将其加载到视图(或布局)中,如下所示:
<%# /app/views/users/index.html.erb %>
<%= stylesheet_link_tag 'users' %>
<%= javascript_include_tag 'users' %>
<%# needed by the `users/_user.html.erb` partial %>
<%= javascript_include_tag '_user' %>
<% render @users %>
主要问题是您必须跟踪所有这些资源,并确保所有需要它们的视图都加载每个资源,但也要避免在不需要时加载它们。这可能是一个真正的痛苦,尤其是当您有很多视图时。
当使用 Proscenium 侧面加载 JavaScript、Typescript 和 CSS 时,它们会自动包含在您的视图、部分、布局和组件旁边,并且仅在需要时包含。
侧面加载的工作原理是查找与视图、部分、布局或组件同名的 JS/TS/CSS 文件。例如,如果您有一个位于app/views/users/index.html.erb
的视图,那么 Proscenium 将在app/views/users/index.js
、 app/views/users/index.ts
处查找 JS/TS/CSS 文件app/views/users/index.ts
或app/views/users/index.css
。如果找到,它会将其包含在该视图的 HTML 中。
JavaScript 和 Typescript 也支持 JSX。只需使用.jsx
或.tsx
扩展名而不是.js
或.ts
。
只需创建一个与任何视图、部分或布局同名的 JS 和/或 CSS 文件。
让我们继续上面的问题示例,其中我们有以下资产
/app/assets/application.css
/app/assets/application.js
/app/assets/users.css
/app/assets/users.js
/app/assets/user.js
您的应用程序布局位于/app/views/layouts/application.hml.erb
,需要用户资源的视图位于/app/views/users/index.html.erb
,因此将您的资源 JS 和 CSS 与它们一起移动:
/app/views/layouts/application.css
/app/views/layouts/application.js
/app/views/users/index.css
/app/views/users/index.js
/app/views/users/_user.js
(部分)现在,在您的布局和视图中,将javascript_include_tag
和stylesheet_link_tag
帮助程序替换为 Proscenium 中的include_asset
帮助程序。像这样的事情:
<!DOCTYPE html >
< html >
< head >
< title > Hello World </ title >
<%= include_assets # <-- %>
</ head >
< body >
<%= yield %>
</ body >
</ html >
在每个页面请求中,Proscenium 都会检查您的任何视图、布局和部分是否具有同名的 JS/TS/CSS 文件,然后将它们包含在您放置include_assets
帮助程序的位置。
现在您再也不用记得添加您的资产了。只需将它们与您的视图、局部和布局一起创建,Proscenium 就会处理剩下的事情。
默认情况下启用侧面加载,但您可以通过在/config/application.rb
中将config.proscenium.side_load
设置为false
来禁用它。
还有include_stylesheets
和include_javascripts
帮助程序,可让您控制 CSS 和 JS 资源包含在 HTML 中的位置。如果您想准确控制资产的包含位置,则应使用这些帮助程序而不是include_assets
。
捆绑文件意味着将任何导入的依赖项内联到文件本身中。这个过程是递归的,因此依赖项的依赖项(等等)也将被内联。
Proscenium 将默认实时捆绑。因此没有单独的构建步骤或预编译。
Proscenium 支持从 NPM、通过 URL、本地应用程序甚至其他 Ruby Gem 导入 JS、JSX、TS、TSX、CSS 和 SVG。
JavaScript 和 TypeScript 支持静态 ( import
) 和动态 ( import()
) 导入,并且可用于导入 JS、TS、JSX、TSX、JSON、CSS 和 SVG 文件。
CSS 支持@import
CSS at 规则。
目前,仅当导入路径是字符串文字或全局模式时才会进行捆绑。其他形式的导入路径不会捆绑,而是逐字保留在生成的输出中。这是因为捆绑是一个编译时操作,而 Proscenium 不支持所有形式的运行时路径解析。
以下是一些示例:
// Analyzable imports (will be bundled)
import "pkg" ;
import ( "pkg" ) ;
import ( `./locale- ${ foo } .json` ) ;
// Non-analyzable imports (will not be bundled)
import ( `pkg/ ${ foo } ` ) ;
解决不可分析导入的方法是将包含此有问题的代码的包标记为未捆绑,以便它不包含在捆绑包中。然后,您需要确保外部包的副本在运行时可用于您的捆绑代码。
node_modules
)完全支持裸导入(不以./
、 /
、 https://
、 http://
开头的导入),并将通过位于的package.json
文件使用您选择的包管理器(例如,NPM、Yarn、pnpm)在 Rails 应用程序的根目录下。
使用您选择的包管理器安装您想要导入的包...
npm install react
...然后像导入任何其他包一样导入它。
import React from "react" ;
当然,您可以使用相对或绝对路径导入您自己的代码(文件扩展名是可选的,绝对路径使用您的 Rails 根目录作为基础):
import utils from "/lib/utils" ;
import constants from "./constants" ;
import Header from "/app/components/header" ;
@import "/lib/reset" ;
有时您不想捆绑导入。例如,您希望确保仅加载一个 React 实例。在这种情况下,您可以使用unbundle
前缀
import React from "unbundle:react" ;
这仅适用于任何裸露和本地进口。
您还可以在导入映射中使用unbundle
前缀,这可确保特定路径的所有导入始终未捆绑:
{
"imports" : {
"react" : " unbundle:react "
}
}
然后像平常一样导入:
import React from "react" ;
[开发中]
开箱即用地支持 JS 和 CSS 的导入映射,并且与所使用的浏览器无关。这是因为导入映射是由服务器上的 Proscenium 解析和解析的,而不是由浏览器解析。这更快,并且还允许您在尚不支持导入地图的浏览器中使用导入地图。
如果您不熟悉导入映射,请将它们视为定义别名的一种方法。
只需创建config/import_map.json
并指定要使用的导入。例如:
{
"imports" : {
"react" : " https://esm.sh/[email protected] " ,
"start" : " /lib/start.js " ,
"common" : " /lib/common.css " ,
"@radix-ui/colors/" : " https://esm.sh/@radix-ui/[email protected]/ "
}
}
使用上面的导入映射,我们可以做...
import { useCallback } from "react" ;
import startHere from "start" ;
import styles from "common" ;
对于CSS...
@import "common" ;
@import "@radix-ui/colors/blue.css" ;
您还可以使用 JavaScript 而不是 JSON 编写导入映射。因此,不要创建config/import_map.json
,而是创建config/import_map.js
,并定义一个匿名函数。该函数接受单个environment
参数。
( env ) => ( {
imports : {
react :
env === "development"
? "https://esm.sh/[email protected]?dev"
: "https://esm.sh/[email protected]" ,
} ,
} ) ;
源映射可以让您更轻松地调试代码。它们对从生成的输出文件中的行/列偏移量转换回相应原始输入文件中的行/列偏移量所需的信息进行编码。如果您生成的代码与原始代码有很大不同(例如您的原始代码是 TypeScript 或您启用了缩小),这非常有用。如果您更喜欢在浏览器的开发人员工具中查看单个文件而不是一个大的捆绑文件,这也很有用。
JavaScript 和 CSS 支持源映射输出。每个文件都附加了源映射的链接。例如:
//# sourceMappingURL=/app/views/layouts/application.js.map
您的浏览器开发工具应该选择此功能并在需要时自动加载源映射。
您可以从 JS(X) 导入 SVG,这将捆绑 SVG 源代码。此外,如果从 JSX 或 TSX 导入,SVG 源代码将呈现为 JSX/TSX 组件。
适用于
>=0.10.0
您可以在proscenium.env
命名空间下从 JavaScript 和 Typescript 定义和访问任何环境变量。
出于性能和安全原因,您必须在config/application.rb
文件中声明您希望公开的环境变量名称。
config . proscenium . env_vars = Set [ 'API_KEY' , 'SOME_SECRET_VARIABLE' ]
config . proscenium . env_vars << 'ANOTHER_API_KEY'
这假设已经定义了同名的环境变量。如果没有,您需要在代码中使用 Ruby 的ENV
对象或在 shell 中自行定义它。
这些声明的环境变量将被替换为常量表达式,允许您像这样使用它:
console . log ( proscenium . env . RAILS_ENV ) ; // console.log("development")
console . log ( proscenium . env . RAILS_ENV === "development" ) ; // console.log(true)
RAILS_ENV
和NODE_ENV
环境变量将始终自动为您声明。
除此之外,Proscenium 还提供了一个process.env.NODE_ENV
变量,该变量设置为与proscenium.env.RAILS_ENV
相同的值。提供它是为了支持社区现有的工具,这些工具通常依赖于该变量。
环境变量在帮助 Tree Shaking 方面特别强大。
function start ( ) {
console . log ( "start" ) ;
}
function doSomethingDangerous ( ) {
console . log ( "resetDatabase" ) ;
}
proscenium . env . RAILS_ENV === "development" && doSomethingDangerous ( ) ;
start ( ) ;
在开发中,上面的代码将被转换为以下代码,丢弃定义,并调用doSomethingDangerous()
。
function start ( ) {
console . log ( "start" ) ;
}
start ( ) ;
请注意,出于安全原因,URL 导入中不会替换环境变量。
未定义的环境变量将被替换为undefined
。
console . log ( proscenium . env . UNKNOWN ) ; // console.log((void 0).UNKNOWN)
这意味着依赖于此的代码不会进行树摇动。您可以使用可选的链接运算符来解决此问题:
if ( typeof proscenium . env ?. UNKNOWN !== "undefined" ) {
// do something if UNKNOWN is defined
}
提供了从config/locales/*.yml
导入 Rails 区域设置文件并将其导出为 JSON 的基本支持。
import translations from "@proscenium/i18n" ;
// translations.en.*
默认情况下,Proscenium 的输出将利用 ES2022 规范及更早版本中的所有现代 JS 功能。例如, a !== void 0 && a !== null ? a : b
会变成a ?? b
缩小时(在生产中默认启用),它使用 ES2020 版本的 JavaScript 语法。 ES2020 不支持的任何语法功能都将转换为更广泛支持的旧 JavaScript 语法。
Tree Shaking 是 JavaScript 社区用于消除死代码的术语,这是一种常见的编译器优化,可以自动删除无法访问的代码。在 Proscenium 中默认启用 Tree Shaking。
function one ( ) {
console . log ( "one" ) ;
}
function two ( ) {
console . log ( "two" ) ;
}
one ( ) ;
上面的代码将转换为下面的代码,丢弃two()
,因为它从未被调用。
function one ( ) {
console . log ( "one" ) ;
}
one ( ) ;
适用于
>=0.10.0
。
侧面加载的资源会自动进行代码分割。这意味着,如果您有一个文件被导入并多次使用,并且被不同的文件导入,那么它将被分割成一个单独的文件。
举个例子:
// /lib/son.js
import father from "./father" ;
father ( ) + " and Son" ;
// /lib/daughter.js
import father from "./father" ;
father ( ) + " and Daughter" ;
// /lib/father.js
export default ( ) => "Father" ;
son.js
和daughter.js
都导入father.js
,因此儿子和女儿通常都会包含father 的副本,从而导致重复的代码和更大的包大小。
如果这些文件是侧面加载的,那么father.js
将被分割成一个单独的文件或块,并且只下载一次。
多个入口点之间共享的代码被分成两个入口点导入的单独的共享文件。这样,如果用户首先浏览一个页面,然后浏览另一个页面,如果共享部分已经被浏览器下载并缓存,他们就不必从头开始下载第二个页面的所有 JavaScript。
通过异步import()
表达式引用的代码将被拆分到一个单独的文件中,并且仅在计算该表达式时加载。这使您可以通过仅在启动时下载所需的代码,然后在以后需要时延迟下载其他代码,从而缩短应用程序的初始下载时间。
如果没有代码分割, import() 表达式将变成Promise.resolve().then(() => require())
。这仍然保留了表达式的异步语义,但这意味着导入的代码包含在同一个包中,而不是拆分到单独的文件中。
默认情况下启用代码分割。您可以通过在应用程序的/config/application.rb
中将code_splitting
配置选项设置为false
来禁用它:
config . proscenium . code_splitting = false
就 JavaScript 而言,有一些重要的注意事项。这些在 esbuild 网站上有详细介绍。
CSS 是 Proscenium 中的一流内容类型,这意味着它可以直接捆绑 CSS 文件,而无需从 JavaScript 代码导入 CSS。您可以使用url()
@import
其他 CSS 文件和参考图像和字体文件,Proscenium 会将所有内容捆绑在一起。
请注意,默认情况下,Proscenium 的输出将利用所有现代 CSS 功能。例如, color: rgba(255, 0, 0, 0.4)
在生产中缩小后将变为color: #f006
,这使用了 CSS Color Module Level 4 的语法。
支持新的 CSS 嵌套语法,并将其转换为适用于旧版浏览器的非嵌套 CSS。
Proscenium 还会自动插入供应商前缀,以便您的 CSS 可以在旧版浏览器中运行。
您还可以从 JavaScript 导入 CSS。当您执行此操作时,Proscenium 会自动将每个样式表作为<link>
元素附加到文档的头部。
import "./button.css" ;
export let Button = ( { text } ) => {
return < div className = "button" > { text } < / div > ;
} ;
Proscenium 实现了 CSS 模块的子集。它支持:local
和:global
关键字,但不支持composes
属性。 (建议您使用 mixins 而不是composes
,因为它们可以在任何地方使用,甚至在纯 CSS 文件中也是如此。)
为任何 CSS 文件提供.module.css
扩展名,Proscenium 会将其视为 CSS 模块,并使用该文件特有的后缀转换所有类名。
. title {
font-size : 20 em ;
}
上述输入产生:
. title-5564cdbb {
font-size : 20 em ;
}
您现在拥有一个几乎可以在任何地方使用的唯一类名称。
您可以使用css_module
帮助程序从 Rails 视图、部分视图和布局中引用 CSS 模块,该帮助程序接受一个或多个类名称,并将返回等效的 CSS 模块名称 - 附加了唯一后缀的类名称。
通过侧面加载设置,您可以使用css_module
帮助程序,如下所示。
< div >
< h1 class =" <%= css_module :hello_title %> " > Hello World </ h1 >
< p class =" <%= css_module :body , paragraph : %> " >
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</ p >
</ div >
css_module
接受多个类名,并将返回转换后的 CSS 模块名称的空格分隔字符串。
css_module :my_module_name
# => "my_module_name-ABCD1234"
您甚至可以通过将 URL 路径传递给文件(作为类名的前缀)来引用任何 CSS 文件中的类。这样做会自动侧面加载样式表。
css_module '/app/components/button.css@big_button'
# => "big_button"
它还支持 NPM 包(已安装在 /node_modules 中):
css_module 'mypackage/button@big_button'
# => "big_button"
css_module
还接受一个path
关键字参数,它允许您指定 CSS 文件的路径。请注意,这将使用传递给css_module
实例的所有类名的给定路径。
css_module :my_module_name , path : Rails . root . join ( 'app/components/button.css' )
从 JS 导入 CSS 模块会自动将样式表附加到文档的头部。导入的结果将是 CSS 类到模块名称的对象。
import styles from "./styles.module.css" ;
// styles == { header: 'header-5564cdbb' }
需要注意的是,CSS 模块名称的导出对象实际上是一个 JavaScript 代理对象。所以解构对象是行不通的。相反,您必须直接访问属性。
此外,将一个 CSS 模块导入另一个 CSS 模块将导致所有类产生相同的摘要字符串。
Proscenium 提供了将一个或多个 CSS 类包含或“混合”到另一个类中的功能。这类似于 CSS 模块的composes
属性,但适用于任何地方,并且不限于 CSS 模块。
使用@define-mixin
和@mixin
at-rules 支持 CSS mixin。
mixin 是使用@define-mixin
at 规则定义的。为其传递一个名称,该名称应遵循类名称语义,并声明您的规则:
// /lib/mixins.css
@define-mixin bigText {
font-size : 50 px ;
}
使用@mixin
at 规则来使用 mixin。向其传递您要使用的 mixin 的名称以及声明 mixin 的 url。 url 用于解析 mixin,可以是相对的、绝对的、URL,甚至来自 NPM 包。
// /app/views/layouts/application.css
p {
@mixin bigText from url ( "/lib/mixins.css" );
color : red;
}
上面产生了这个输出:
p {
font-size : 50 px ;
color : red;
}
Mixins 可以在任何 CSS 文件中声明。它们不需要在使用它们的同一文件中声明。但是,如果您在同一文件中声明并使用 mixin,则无需指定声明 mixin 的 URL。
@define-mixin bigText {
font-size : 50 px ;
}
p {
@mixin bigText;
color : red;
}
CSS 模块和 Mixins 完美配合。您可以在 CSS 模块中包含 mixin。
就 CSS 而言,有一些重要的注意事项。这些在 esbuild 网站上有详细介绍。
Typescript 和 TSX 是开箱即用的,并且具有解析 TypeScript 语法和丢弃类型注释的内置支持。只需将文件重命名为.ts
或.tsx
即可。
请注意,Proscenium 不执行任何类型检查,因此您仍然需要与 Proscenium 并行运行tsc -noEmit
来检查类型。
就 Typescript 而言,有一些重要的警告。这些在 esbuild 网站上有详细介绍。
使用 JSX 语法通常需要您手动导入正在使用的 JSX 库。例如,如果您使用 React,默认情况下您需要将 React 导入到每个 JSX 文件中,如下所示:
import * as React from "react" ;
render ( < div / > ) ;
这是因为 JSX 转换将 JSX 语法转换为对React.createElement
的调用,但它本身并不导入任何内容,因此 React 变量不会自动存在。
Proscenium 为您生成这些导入语句。请记住,这也完全改变了 JSX 转换的工作方式,因此如果您使用的 JSX 库不是 React,则可能会破坏您的代码。
在[不太遥远]的将来,您将能够配置 Proscenium 以使用不同的 JSX 库,或完全禁用此自动导入。
导入 .json 文件会将 JSON 文件解析为 JavaScript 对象,并将该对象作为默认导出导出。使用它看起来像这样:
import object from "./example.json" ;
console . log ( object ) ;
除了默认导出之外,JSON 对象中的每个顶级属性也有命名导出。直接导入命名导出意味着 Proscenium 可以自动从包中删除 JSON 文件中未使用的部分,只留下您实际使用的命名导出。例如,此代码在捆绑时仅包含版本字段:
import { version } from "./package.json" ;
console . log ( version ) ;
Phlex 是一个用纯 Ruby 构建快速、可重用、可测试视图的框架。 Proscenium 与 Phlex 完美配合,支持侧面加载、CSS 模块等。只需编写您的 Phlex 类并从Proscenium::Phlex
继承即可。
class MyView < Proscenium :: Phlex
def view_template
h1 { 'Hello World' }
end
end
在您的布局中,包含Proscenium::Phlex::AssetInclusions
,并调用include_assets
帮助程序。
class ApplicationLayout < Proscenium :: Phlex
include Proscenium :: Phlex :: AssetInclusions # <--
def view_template ( & )
doctype
html do
head do
title { 'My Awesome App' }
include_assets # <--
end
body ( & )
end
end
end
您可以使用include_stylesheets
和include_javascripts
帮助程序专门包含 CCS 和 JS 资源,从而允许您控制它们包含在 HTML 中的位置。
任何继承Proscenium::Phlex
的 Phlex 类都会自动被侧面加载。
Phlex 类完全支持 CSS 模块,如果需要,可以访问css_module
帮助器。然而,有一种更好、更简单的方法来在 Phlex 类中引用 CSS 模块类。
在您的 Phlex 类中,任何以@
开头的类名都将被视为 CSS 模块类。
# /app/views/users/show_view.rb
class Users :: ShowView < Proscenium :: Phlex
def