@jodolrui/glue v1.0.6
Glue (total feature separation in Vue 3 Composition API)
Glue provides total feature separation in Vue 3 Composition API components.
Glue is intended for better code organization and less scrolling.
Installation
npm install @jodolrui/glue
Version
1.0.6 Bug fixed.
1.0.5
defineState
function.1.0.4 Typescript support in
exposed()
function.1.0.3 Add license.
1.0.1 First version.
Example of use
The issue
This is a typical Vue 3 Composition API component with two features (foo
and bar
):
// Foobar.vue
<template>{{ foo }} {{ bar }}</template>
<script>
import { ref } from "vue";
export default {
props: { foo: { type: String, default: "Foo" },
bar: { type: String, default: "Bar" } },
emits: ["foo", "bar"],
setup(props, context) {
let foo = ref(props.foo);
context.emit("foo");
let bar = ref(props.bar);
context.emit("bar");
return { foo, bar };
},
};
</script>
Notice that both features (foo
and bar
) appear mixed at some parts of the component:
- At
props
declaration. - At
emits
declaration. - Throughout
setup
function. - At
return
statement.
Glue solution
With Glue you can totally separate features into parts/files:
foo.js
part/file:
// foo.js
import { ref } from "vue";
export default {
props: { foo: { type: String, default: "Foo" } },
emits: ["foo"],
setup(props, context) {
let foo = ref(props.foo);
context.emit("foo");
return { foo };
},
};
bar.js
part/file:
// bar.js
import { ref } from "vue";
export default {
props: { bar: { type: String, default: "Bar" } },
emits: ["bar"],
setup(props, context) {
let bar = ref(props.bar);
context.emit("bar");
return { bar };
},
};
Note that each part/file is written in normal Vue 3 Composition API syntax, so you don't have to learn anything new to create them.
Finally, parts must to be assembled with function compose
in Foobar.vue
component file:
<!-- Foobar.vue -->
<template>{{ foo }} {{ bar }}</template>
<script>
import { compose } from "@jodolrui/glue";
import foo from "./foo";
import bar from "./bar";
export default compose("Foobar", [foo, bar]);
</script>
Function compose
takes two parameters:
- The
name
of the component (i.e."Foobar"
). - An array of
parts
(i.e.[foo, bar]
).
Warning: Order of parts in array is very important because it defines order of execution.
You have to import Foobar.vue
file from your parent component as usual in order to use it:
import Foobar from './Foobar.vue'
// ...
<Foobar />
Exposing variables and functions to the template
The issue
This is a typical declaration of a variable exposed to the <template>
in a Vue 3 Composition API component:
import { ref } from "vue";
export default {
setup() {
let foo = ref("bar");
return { foo };
},
};
Notice that you have to:
- Declare the variable (
let foo
) and assign it a value (= ref("bar")
) return
a literal object containing the variable (return { foo }
)
Glue solution
Glue function expose
allows you to achieve the same without having to return
:
import { ref } from "vue";
import { expose } from "@jodolrui/glue";
export default {
setup() {
expose("foo", ref("bar"));
},
};
In this case, function expose
takes two parameters:
- The
key
or name by which the element will be referred in the<template>
(i.e."foo"
). - The
object
of the element itself (i.e.ref("bar")
).
Function expose
returns the passed element itself, so you can assign it to a variable:
import { expose } from "@jodolrui/glue";
// ...
const foo = expose("foo", ref("bar"));
Alternative syntax
Another syntax for expose
is:
import { expose } from "@jodolrui/glue";
// ...
const foo = ref("bar");
expose({ foo });
In this case function expose
takes only one parameter: a literal object
containing elements to expose.
The advantage of this syntax is that you can expose multiple elements in one line:
import { expose } from "@jodolrui/glue";
// ...
const foo = ref("bar");
const bar = () => console.log(`value is ${foo.value}`);
expose({ foo, bar });
Notice that function expose
can be called throughout function setup
, so that you can expose
elements at the very time they are defined or immediately thereafter. This strengthens feature separation as yout don't need to put all them in a return
statement at the end of setup
function.
Sharing variables and functions between parts or components
Elements exposed with Glue can be imported into another parts or components.
An exposed element like this:
const foo = ref("bar");
expose({ foo });
or like this:
const foo = ref("bar");
return { foo };
can be imported into another part of the same component calling function exposed()
:
import { exposed } from "@jodolrui/glue";
// ...
const { foo } = exposed();
or this way:
import { exposed } from "@jodolrui/glue";
// ...
exposed().foo;
You can also import with typescript
support this way:
import { exposed } from "@jodolrui/glue";
// ...
type Type = {
foo: Ref<string>;
}
// ...
const { foo } = exposed<Type>();
To import from another component exposed
has to take one parameter:
import { exposed } from "@jodolrui/glue";
// ...
const { foo } = exposed("Foobar");
The parameter is the name
of the component to import from (i.e. "Foobar"
).
It's important to know that both components must to be created with function compose
in order this to work.
Notice that only previously exposed elements can be imported, so that order of component mounting and order of parts in the array passed to function compose
are determining.
If you try to retrieve an nonexistent exposed element (or a misspelled one), Glue will throw an error:
Glue error Unknown key 'foo' in 'exposed' function.
Limitations on the use of functions 'expose' and 'exposed'
Function expose
and function exposed
referring to the same component (aka exposed()
) only work during setup or lifecycle hooks, as they internally make use of the Vue function getCurrentInstance
, wich has such limitation. So Glue will throw an error if they are used in invalid scopes:
Glue error Cannot use 'expose' in this scope.
Glue error Cannot use 'exposed' in this scope.
If you need to use expose
or exposed()
outside setup or lifecycle hooks, you can call them on setup and use the instance instead.
Typescript errors in *.vue file
If using Glue with typescript it is possible that your IDE show errors in the *.vue
file indicating that variables you exposed to the <template>
are unknown. This issue doesn't break the application, which should work correctly, but they can be annoying. To avoid this problem I suggest disabling typescript in the *.vue
.
Remove *.vue
extension from tsconfig.json
:
// "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx"]
Remove reference to typescript language from *.vue
file:
// <script lang="ts">
<script>
Separation of html and css
If you want to separate html
and css
out of the *.vue
file, you can do something like this:
<!-- index.vue -->
<script src="./script.js"></script>
<style scoped src="./style.css"></style>
<template src="./template.html"></template>
// script.js
import { compose } from "@jodolrui/glue";
import foo from "./parts/foo";
export default compose("Foo", [foo]);
// parts/foo.js
import { ref } from "vue";
import { expose } from "@jodolrui/glue";
export default {
props: { foo: { type: String, default: "Foo" } },
emits: ["foo"],
setup(props, context) {
expose("foo", ref(props.foo));
context.emit("foo");
},
};
<!-- template.html -->
<p>{{ foo }}</p>
/* type.css */
* {
color: black;
}
Using with <script setup>
You can use glue
with <script setup>
syntactic sugar.
You only have to put your parts into *.vue
files within a <script setup>
tag and then assemble them with compose
function as usual.
Here you have an example:
foo.vue
part/file:
<!-- foo.vue -->
<script setup>
import { ref } from "vue";
let foo = ref("Foo");
</script>
bar.vue
part/file:
<!-- bar.vue -->
<script setup>
import { ref } from "vue";
import { exposed } from "@jodolrui/glue";
let bar = ref("Bar");
let { foo } = exposed();
let foobar = ref(foo.value + bar.value);
</script>
Assemble with function compose
in Foobar.vue
component file:
<!-- Foobar.vue -->
<template>{{foo}} + {{bar}} = {{ foobar }}</template> <!-- prints "Foo + Bar = FooBar" -->
<script>
import { compose } from "@jodolrui/glue";
import foo from "./foo.vue"; // don't forget .vue extension
import bar from "./bar.vue"; // don't forget .vue extension
export default compose("Foobar", [foo, bar]);
</script>
Note that using <script setup>
you don't need to use expose
function.
Centralizing component state with 'defineState'
Function defineState
allows you to centralize component state declaration and preseting in one place and use it with typescript
types in all your component parts.
You have to declare and preset your state variables creating a useState
function this way:
// state.ts
import { defineState } from "glue";
import { Ref, ref } from "vue";
export function useState() {
return defineState<{
foo: Ref<string>;
bar: Ref<string>;
}>({
foo: ref("Foo"),
bar: ref("Bar")
});
}
Then you can access your state calling useState
function from any component part:
// foo.js
import { ref } from "vue";
import { useState } from "../state";
export default {
props: { foo: { type: String, default: "Foo" } },
emits: ["foo"],
setup(props, context) {
const state = useState(); // state is typed
state.foo.value = props.foo;
context.emit("foo");
},
};
You don't need to return
, expose
nor exposed
when using state centralization with defineState
and useState
.