Skip to content

Commit

Permalink
Merge pull request #3 from sebamarynissen/feature/script-setup
Browse files Browse the repository at this point in the history
Fix `<script setup>` in Vue 2.7
  • Loading branch information
sebamarynissen authored Oct 15, 2023
2 parents b4bbcba + c5a8f6f commit 10f6711
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 17 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: CI

on:
push:
pull_request:
on: pull_request

jobs:
test-node:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Publish

on:
release:
types: [created]

jobs:
publish-npm:
name: Publish to npm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 18
registry-url: https://registry.npmjs.org/
cache: npm
- run: npm install -g npm@latest
- run: npm i
- run: npm test
- run: npm version ${TAG_NAME} --git-tag-version=false
env:
TAG_NAME: ${{ github.event.release.tag_name }}
- run: npm whoami; npm --ignore-scripts publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
34 changes: 27 additions & 7 deletions lib/compile-template.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// # compile-template.js
import { parse as parseURL } from 'node:url';
import qs from 'node:querystring';
import { fileURLToPath } from 'node:url';
import { resolveCompiler } from './compiler.js';
import { major } from './vue-version.js';
import { getDescriptor } from './descriptor-cache.js';
import resolveScript from './resolve-script.js';
import { major, minor } from './vue-version.js';

// # compile(tpl, ctx)
// Compiles the template. Note that we still need to check a few things in the
Expand All @@ -11,19 +12,38 @@ export default function compile(tpl, ctx) {

// Parse the query string from the url first and build the compiler
// options from it.
let url = new parseURL(ctx.url);
let query = qs.parse(url.query || '');
let isFunctional = Boolean(query.functional);
let url = new URL(ctx.url);
let query = url.searchParams;
let isFunctional = query.get('functional') !== null;
let compilerOptions = {};

// IMPORTANT! When using <script setup> in Vue 2.7, we have to explicitly
// inject the bindings into the template compiler. Therefore we need to
// lookup the dedcriptor in the cache if that's the case.
let bindings;
if (major === 2 && minor >= 7) {
let filePath = fileURLToPath(String(url));
let descriptor = getDescriptor(filePath);
if (!descriptor) {
console.warn([
`[WARN] No descriptor found for file "${filePath}". This is likely because you're using an external template for which we couldn't find the corresponding .vue file. Exports from <script setup> might not be available. It is recommended to not use external templates in combination with <script setup>.`,
].join('\n'));
}
if (descriptor.scriptSetup) {
let script = resolveScript(descriptor);
bindings = script.bindings;
}
}

const { compiler, templateCompiler } = resolveCompiler();
let source = String(tpl);
let compiled = compiler.compileTemplate({
source,
compilerOptions,
isFunctional,
...templateCompiler && { compiler: templateCompiler() },
...query.id && { id: query.id },
...query.get('id') !== null && { id: query.get('id') },
...bindings && { bindings },
});

// At last return the code. IMPORTANT! In v2 we have to add the exports
Expand Down
15 changes: 10 additions & 5 deletions lib/create-facade-2.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// # create-facade-2.js
import hash from 'hash-sum';
import path from 'node:path';
import { stringifyRequest, attrsToQuery } from './utils.js';
import { getDescriptor, setDescriptor } from './descriptor-cache.js';
import selectBlock from './select.js';
import { resolveCompiler } from './compiler.js';

// # createFacade(opts)
// This function is called when we're actually transforming the .vue file
Expand All @@ -13,12 +14,10 @@ export default function createFacade(opts = {}) {
// Parse an SFC descriptor. We'll always need this, regardless of what
// we're about to do anyway.
const { source, filename, filePath, sourceRoot, query, ctx } = opts;
const { compiler, templateCompiler } = resolveCompiler();
let descriptor = compiler.parse({
const descriptor = getDescriptor(filePath, {
source,
filename,
sourceRoot,
...templateCompiler && { compiler: templateCompiler() },
version: 2,
});

// If we're loading something from the facade module, we will return early
Expand Down Expand Up @@ -46,8 +45,14 @@ export default function createFacade(opts = {}) {
// Add the code importing the template.
let templateImport = `var render, staticRenderFns;`;
if (descriptor.template) {

// IMPORTANT! If we're loading the template from an external file, we
// have to store the desrciptor in the cache under that filename as well.
let src;
if (descriptor.template.src) {
let dir = path.dirname(filePath);
let templatePath = path.resolve(dir, descriptor.template.src);
setDescriptor(templatePath, descriptor);
let id = descriptor.template.src;
src = `${id}?vue`;
} else {
Expand Down
54 changes: 54 additions & 0 deletions lib/descriptor-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// # descriptor-cache.js
import fs from 'node:fs';
import { resolveCompiler } from './compiler.js';
import vueVersion from './vue-version.js';

const cache = new Map();

// # getDescriptor(filename, opts)
// The descriptor cache is something we need to get <script setup> working
// properly. The problem is that the bindings from script setup need to be
// injected in the template, but that means we need to be able to load the
// descriptor again **from within the template**. Hence we abstract away getting
// the descriptor and cache them by filename.
export function getDescriptor(filename, opts = {}) {

// Check the cache first.
if (cache.has(filename)) {
return cache.get(filename);
}

// If the source is somehow not known, it might be because loaders are
// running in different threads. Not sure if node does this, but let's be
// sure.
const {
source = fs.readFileSync(filename),
version = vueVersion,
} = opts;

// Create the descriptor based on the version.
if (version === 2) {
const { sourceRoot } = opts;
const { compiler, templateCompiler } = resolveCompiler();
let descriptor = compiler.parse({
source,
filename,
sourceRoot,
...templateCompiler && { compiler: templateCompiler() },
});
cache.set(filename, descriptor);
return descriptor;
}

// Vue 3 by default.
const { compiler } = resolveCompiler();
let { descriptor } = compiler.parse(source, { filename });
cache.set(filename, descriptor);
return descriptor;

}

// # setDescriptor(filename, descriptor)
export function setDescriptor(filename, descriptor) {
cache.set(filename, descriptor);
}
3 changes: 3 additions & 0 deletions test/core/files/custom-component.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<p>Hello</p>
</template>
7 changes: 6 additions & 1 deletion test/core/files/setup.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<template>
<div>Hello world</div>
<div>
Hello world
<custom-component />
</div>
</template>

<script>
import CustomComponent from './custom-component.vue';
export default {
foo: 'bar',
};
Expand Down
18 changes: 17 additions & 1 deletion test/core/loader-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// # loader-test.js
import path from 'node:path';
import semver from 'semver';
import { expect } from 'chai';
import version from '#vue/version';
Expand Down Expand Up @@ -72,6 +71,23 @@ describe('The vue esm loader', function() {
expect(component.setup({}, {
expose() {},
}).foo).to.equal('baz');

// Test that the components are properly available when using script
// setup. Depends on whether we're using 2 or 3 obviously.
const { render } = component;
if (semver.satisfies(version, '2')) {
let result = render.call({
_self: {
_c: (...args) => args,
_setupProxy: {
CustomComponent: 'this is it',
},
},
_v: x => x,
}).flat(Infinity);
expect(result).to.include('this is it');
}

});

it('external-script.vue', async function() {
Expand Down

0 comments on commit 10f6711

Please sign in to comment.