Extended `@block` Reference Syntax
See original GitHub issueSummary
Implementation of this proposal will enable the following features:
- Rename
@block-reference
to@block
- Additions to the
@block
import syntax- Default
@block
imports from a Block module - Named
@block
imports from a Block module - Default Block exports from a module
- Named Block exports from a module
- Default
-
default
as a reserved name -
css-blocks
package.json config field- Custom Block module “main” path
- CSS Blocks module resolution algorithm
- Relative Block import path resolution
- Absolute Block import path resolution
-
node_module
Block import path resolution
Detailed design
Rename @block-reference
to @block
After some time out in the wild, we’ve received feedback that typing @block-reference
is onerous and looks weird.
This proposal would update @block-reference
:
@block-reference foobar from "path/to/file.block.css";
To @block
:
@block foobar from "path/to/file.block.css";
This has the added benefit of providing the option of backwards compatibility / deprecation for this feature change, although we are pre-1.0 and this is not required.
@block
Imports
To enable a more robust module delivery system in css-blocks, this proposal would define the API options for @block
as follows.
Token | Definition | |
---|---|---|
<block-import> |
::= | " @block " <blocks-list> " from " <block-path> |
<blocks-list> |
::= | <default-block> | <named-blocks> | <default-block> " , " <named-blocks> |
<default-block> |
::= | <ident> |
<named-blocks> |
::= | " ( " <named-ref> { " , " <named-ref> } " ) " |
<named-ref> |
::= | <local-name> | <aliased-block> |
<aliased-block> |
::= | <ident> " as " <local-name> |
<local-name> |
::= | <ident> (Note: Lexer restriction – cannot be “default”) |
<block-path> |
::= | ’ " ’ <any-value> ’ " ’ | " ’ " <any-value> " ’ " |
Examples of this grammars use are
Default @block
imports from a Block module
@block local-name from "path/to/block.css";
In the above example, the default exported block from the block file discovered at <block-path>
will be available under the local name local-name
.
Named @block
imports from a Block module
To import a named export of a Block file:
/* Direct named import */
@block ( other-block ) from "path/to/block.css";
/* Aliased named import */
@block ( other-block as local-name ) from "path/to/block.css";
/* Alternate default block import – the following two lines are equivalent! */
@block ( default as local-name ) from "path/to/block.css";
@block local-name from "path/to/block.css";
/* Multiple named imports */
@block (
other-block-1 as local-name,
other-block-2,
other-block-3 as local-name-2
) from "path/to/block.css";
/* Multiple named imports with default import */
@block other-block, (
other-block-1 as local-name,
other-block-2,
other-block-3 as local-name-2
) from "path/to/block.css";
@block
Exports
Default Block exports from a module
All rules defined in the scope of a Block file are the default
export of a block. So, the following file exposes a single block called default
which contains the BlockClasses :scope
and .foo
when imported elsewhere:
:scope { color: red; }
.foo { color: blue; }
Even if there are no declarations in a Block file, there will always be a default block exported.
Named Block exports from a module
Any imported Blocks that need to be made available from the Block file as named exports must be explicitly re-exported. A similar syntax to @block
imports is used.
/* Named Export */
@export block-name;
/* Multiple Named Export */
@export ( block-name-1, block-name-2 );
/* Aliased Export */
@export ( block-name as aliased-name );
/* Block Redirects */
@export block-name, (other-block as aliased-name) from "path/to/a.block.css";
default
as a reserved word
To avoid naming conflicts and import/export ambiguity, user will be unable to import or export blocks under the local name default
. If this were allowed, it would become ambiguous how the imported default
block would interact with the local Block definition, also called default
. The following would throw a build time error of “Error: ‘default’ is a reserved word.”:
/* Error: Can not import "block-1" as reserved word "default". */
@block ( block-1 as default ) from "block-1.block.css";
/* Error: Default Block from "block-1.block.css" must be aliased to a unique local identifier. */
@block ( default ) from "block-1.block.css";
/* Error: Can not export "block-1" as reserved word "default". */
@export ( block-1 as default ) from "block-1.block.css";
Execution order and name conflicts
@block
imports are statically defined (I mean, what else is CSS good for) and parsed in order of appearance before the default Block’s contents are parsed. Because of this, you can imagine that all @block
references are hoisted at runtime and processed first, regardless of location in the file.
Local identifier values will be assigned Block references in order of execution. Duplicate identifiers will have their values overwritten.
@block ( block-1 as foo ) from "block-1.block.css";
:scope {
extends: foo; /* Value of `foo` is `block-2b` */
}
@block ( block-2a as foo, block-2b as foo ) from "block-2.block.css";
css-blocks
package.json config field
Modules that deliver Block files may choose to add an optional css-blocks: { ... }
configuration field to their package.json
s. Here they may define configuration options for their module. At the time or writing, there is only one valid property for the options hash:
Key | Default | Definition |
---|---|---|
main | ModuleRoot |
Custom Block module “main” path. Usage defined in the module resolution algorithm below. |
CSS Blocks module resolution algorithm
Heavy inspired (aka: close to outright copied) from the Node.js Resolution Algorithm
import X from module at path Y
- If X is a core module, a. return the core module b. STOP
- If X begins with ‘/’ a. set Y to be the module root (closest package.json)
- If X begins with ‘./’ or ‘/’ or ‘…/’ a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X)
- LOAD_NODE_MODULES(X, dirname(Y))
- THROW “not found”
LOAD_AS_FILE(X)
- If X is a
*.block.css
file, load X as a Block. STOP - If X.block.css is a file, load X.block.css as a Block. STOP
- If there is a preprocessor registered for the file’s extension, preprocess the file and load the result as a Block. STOP
LOAD_INDEX(X)
- If X/index.block.css is a file, load X/index.block.css as JavaScript text. STOP
- If there is a preprocessor registered for the index file’s extension, preprocess the file and load the result as a Block. STOP
LOAD_AS_DIRECTORY(X)
- If X/package.json is a file, a. Parse X/package.json, and look for “css-blocks.main” field. b. let M = X + (json main field) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M)
- LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
- let DIRS = NODE_MODULES_PATHS(START)
- for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
- let PARTS = path split(START)
- let I = count of PARTS - 1
- let DIRS = [GLOBAL_FOLDERS]
- while I >= 0, a. if PARTS[I] = “node_modules” CONTINUE b. DIR = path join(PARTS[0 … I] + “node_modules”) c. DIRS = DIRS + DIR d. let I = I - 1
- return DIRS
Outstanding Questions
- Do we allow for truly private Blocks in a module? This proposal does not allow for that.
Issue Analytics
- State:
- Created 5 years ago
- Comments:16 (15 by maintainers)
I like errors. They are clear and they keep me from doing dumb things and being confused about them. If I lose 1-2 min to scratching my head in confusion, it eats up a lot of the savings of not having to do an easy thing that only saves 5-10 seconds.
You would have to explicitly import
local-block-1
fromblock-1.block.css
intoblock-2-block.css
to have access to it inblock-2
’s scope, so in your above example:Instead, if you import
local-block-1
fromblock-1.block.css
: