├── .nvmrc
├── pnpm-workspace.yaml
├── tests
├── tsconfig.json
├── fixtures
│ ├── SingleRoot.vue
│ ├── MultiRoot.vue
│ ├── MultiRootWithEmptyScript.vue
│ ├── MultiRootWithTemplate.vue
│ ├── MultiRootWithScript.vue
│ └── index.ts
├── rollup-plugin.spec.ts
├── utils
│ ├── webpack.ts
│ └── rollup.ts
├── webpack-loader.spec.ts
└── transformer.spec.ts
├── src
├── index.ts
├── install-vue-frag.js
├── loader.ts
├── plugin.ts
└── transformer.ts
├── .github
├── task-runner.gif
├── ISSUE_TEMPLATE
│ ├── FEATURE_REQUEST.md
│ └── BUG_REPORT.md
└── workflows
│ ├── test.yml
│ └── release.yml
├── .editorconfig
├── examples
├── rollup
│ ├── src
│ │ ├── index.js
│ │ └── FragmentTest.vue
│ ├── www
│ │ └── index.html
│ ├── package.json
│ └── rollup.config.js
├── webpack
│ ├── src
│ │ ├── index.js
│ │ └── FragmentTest.vue
│ ├── www
│ │ └── index.html
│ ├── package.json
│ └── webpack.config.js
└── vite
│ ├── src
│ ├── index.js
│ └── FragmentTest.vue
│ ├── package.json
│ ├── index.html
│ └── vite.config.js
├── jest.config.js
├── tsconfig.json
├── .gitignore
├── LICENSE
├── rollup.config.js
├── package.json
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.16.1
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'examples/*'
--------------------------------------------------------------------------------
/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "..",
3 | "include": ["."],
4 | }
5 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import rollupPlugin from './plugin';
2 |
3 | export { rollupPlugin as vueFrag };
4 |
--------------------------------------------------------------------------------
/tests/fixtures/SingleRoot.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | hello world
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.github/task-runner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/privatenumber/vue-frag-plugin/HEAD/.github/task-runner.gif
--------------------------------------------------------------------------------
/src/install-vue-frag.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import frag from 'vue-frag';
3 |
4 | Vue.directive('frag', frag);
5 |
--------------------------------------------------------------------------------
/tests/fixtures/MultiRoot.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | hello
4 |
5 |
6 | fragments
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
--------------------------------------------------------------------------------
/tests/fixtures/MultiRootWithEmptyScript.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | hello
4 |
5 |
6 | fragments
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/fixtures/MultiRootWithTemplate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | hello
5 |
6 |
7 | fragments
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/rollup/src/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import MultiRootComponent from './FragmentTest.vue';
3 |
4 | new Vue({
5 | el: '#app',
6 | render: h => h(MultiRootComponent),
7 | }).$mount();
8 |
--------------------------------------------------------------------------------
/examples/webpack/src/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import MultiRootComponent from './FragmentTest.vue';
3 |
4 | new Vue({
5 | el: '#app',
6 | render: h => h(MultiRootComponent),
7 | }).$mount();
8 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'es-jest',
3 | transformIgnorePatterns: [
4 | // 'node_modules/.pnpm(?!/(aggregate-error|indent-string|clean-stack|escape-string-regexp))',
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/tests/fixtures/MultiRootWithScript.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | hello
4 |
5 |
6 | fragments
7 |
8 |
9 |
10 |
13 |
--------------------------------------------------------------------------------
/examples/vite/src/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import MultiRootComponent from './FragmentTest.vue';
3 |
4 | const app = new Vue({
5 | render: h => h(MultiRootComponent),
6 | });
7 |
8 | app.$mount('#app');
9 |
--------------------------------------------------------------------------------
/examples/rollup/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/webpack/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/loader.ts:
--------------------------------------------------------------------------------
1 | import { LoaderContext } from 'webpack';
2 | import transformer from './transformer';
3 |
4 | function VueFragLoader(this: LoaderContext, source: string) {
5 | const { code, map } = transformer(source, this.resourcePath);
6 | this.callback(null, code, map);
7 | }
8 |
9 | export default VueFragLoader;
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "isolatedModules": true,
5 | "esModuleInterop": true,
6 | "declaration": true,
7 | "outDir": "dist",
8 | "strict": true,
9 |
10 | // Node 12
11 | "module": "commonjs",
12 | "target": "ES2019"
13 | },
14 | "include": [
15 | "src"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 |
12 | # Dependency directories
13 | node_modules/
14 |
15 | # Output of 'npm pack'
16 | *.tgz
17 |
18 | # dotenv environment variables file
19 | .env
20 | .env.test
21 |
22 | # VSCode
23 | .vscode
24 |
25 | # Distribution
26 | dist
27 |
--------------------------------------------------------------------------------
/examples/vite/src/FragmentTest.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hello
5 |
6 |
7 | Fragments
8 |
9 |
10 |
11 |
12 |
25 |
--------------------------------------------------------------------------------
/examples/rollup/src/FragmentTest.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hello
5 |
6 |
7 | Fragments
8 |
9 |
10 |
11 |
12 |
25 |
--------------------------------------------------------------------------------
/examples/webpack/src/FragmentTest.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hello
5 |
6 |
7 | Fragments
8 |
9 |
10 |
11 |
12 |
25 |
--------------------------------------------------------------------------------
/examples/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-frag-plugin/vite",
3 | "private": true,
4 | "version": "0.0.0",
5 | "files": [],
6 | "scripts": {
7 | "dev": "vite"
8 | },
9 | "dependencies": {
10 | "vue": "^2.6.14",
11 | "vue-frag": "^1.3.1"
12 | },
13 | "devDependencies": {
14 | "vite": "^2.6.7",
15 | "vite-plugin-vue2": "^1.9.0",
16 | "vue-frag-plugin": "link:../..",
17 | "vue-template-compiler": "^2.6.14"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/vite/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { defineConfig } from 'vite';
3 | import { createVuePlugin } from 'vite-plugin-vue2';
4 | import { vueFrag } from 'vue-frag-plugin';
5 |
6 | const config = defineConfig({
7 | resolve: {
8 | alias: {
9 | 'vue-frag': path.resolve(__dirname, 'node_modules/vue-frag'),
10 | },
11 | },
12 | plugins: [
13 | vueFrag(),
14 | createVuePlugin(),
15 | ],
16 | });
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/tests/rollup-plugin.spec.ts:
--------------------------------------------------------------------------------
1 | import { vueFrag } from '..';
2 | import { build } from './utils/rollup';
3 | import { multiRootComponentPath } from './fixtures';
4 |
5 | test('works with vue rollup plugin', async () => {
6 | const output = await build(multiRootComponentPath, (config) => {
7 | config.plugins.unshift(vueFrag());
8 | });
9 |
10 | const Component = eval(output); // eslint-disable-line no-eval
11 | expect(Component).toMatchObject({ _compiled: true });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/utils/webpack.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import type { Configuration } from 'webpack';
3 |
4 | const vueFragInstall = path.resolve(__dirname, '../../dist/install-vue-frag.js');
5 | const vueFragPluginLoader = path.resolve(__dirname, '../../dist/loader.js');
6 |
7 | export function setupVueFragPlugin(config: Configuration) {
8 | config.resolve = {
9 | alias: {
10 | 'vue-frag-plugin/install-vue-frag': vueFragInstall,
11 | },
12 | };
13 | config.resolveLoader = {
14 | alias: {
15 | 'vue-frag-plugin/loader': vueFragPluginLoader,
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/examples/rollup/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-frag-plugin/rollup",
3 | "private": true,
4 | "version": "0.0.0",
5 | "files": [],
6 | "scripts": {
7 | "build": "rollup -c rollup.config.js",
8 | "serve": "sirv www"
9 | },
10 | "dependencies": {
11 | "vue": "^2.6.14",
12 | "vue-frag": "^1.3.1"
13 | },
14 | "devDependencies": {
15 | "@rollup/plugin-node-resolve": "^13.0.6",
16 | "rollup": "^2.58.3",
17 | "rollup-plugin-replace": "^2.2.0",
18 | "rollup-plugin-vue": "^5.1.9",
19 | "sirv-cli": "^1.0.14",
20 | "vue-frag-plugin": "link:../.."
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-frag-plugin/webpack",
3 | "private": true,
4 | "version": "0.0.0",
5 | "files": [],
6 | "scripts": {
7 | "build": "webpack --config webpack.config.js --mode production",
8 | "serve": "sirv www"
9 | },
10 | "dependencies": {
11 | "vue": "^2.6.14",
12 | "vue-frag": "^1.3.1"
13 | },
14 | "devDependencies": {
15 | "sirv-cli": "^1.0.14",
16 | "vue-frag-plugin": "link:../..",
17 | "vue-loader": "^15.9.8",
18 | "vue-template-compiler": "^2.6.14",
19 | "webpack": "^5.61.0",
20 | "webpack-cli": "^4.9.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import { createFilter } from '@rollup/pluginutils';
2 | import { Plugin } from 'rollup';
3 | import transformer from './transformer';
4 |
5 | type Options = {
6 | include?: string;
7 | exclude?: string;
8 | };
9 |
10 | export default function vueFrag(options: Options = {}): Plugin {
11 | const filter = createFilter(
12 | options.include ?? '**/*.vue',
13 | options.exclude,
14 | );
15 |
16 | return {
17 | name: 'vue-frag',
18 |
19 | transform(source, id) {
20 | if (!filter(id)) {
21 | return;
22 | }
23 |
24 | return transformer(source, id);
25 | },
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/examples/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { VueLoaderPlugin } = require('vue-loader');
3 |
4 | /** @type {import('webpack').Configuration} */
5 | const config = {
6 | entry: {
7 | index: './src/index.js',
8 | },
9 |
10 | output: {
11 | path: path.resolve('www/dist'),
12 | clean: true,
13 | },
14 |
15 | resolve: {
16 | modules: [
17 | path.resolve(__dirname, 'node_modules'),
18 | ],
19 | },
20 |
21 | module: {
22 | rules: [
23 | {
24 | test: /\.vue$/,
25 | use: [
26 | 'vue-loader',
27 | 'vue-frag-plugin/loader',
28 | ],
29 | },
30 | ],
31 | },
32 |
33 | plugins: [
34 | new VueLoaderPlugin(),
35 | ],
36 | };
37 |
38 | module.exports = config;
39 |
--------------------------------------------------------------------------------
/examples/rollup/rollup.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { defineConfig } from 'rollup';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import vue from 'rollup-plugin-vue';
5 | import { vueFrag } from 'vue-frag-plugin';
6 | import replace from 'rollup-plugin-replace';
7 |
8 | export default defineConfig({
9 | input: './src/index.js',
10 |
11 | plugins: [
12 | resolve({
13 | moduleDirectories: [path.resolve(__dirname, 'node_modules')],
14 | }),
15 | vueFrag(),
16 | vue(),
17 | replace({
18 | 'process.env.NODE_ENV': JSON.stringify('production'),
19 | }),
20 | ],
21 |
22 | output: {
23 | dir: './www/dist',
24 | format: 'commonjs',
25 | exports: 'auto',
26 | interop: false,
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | labels: 'feature request'
5 | ---
6 |
7 | ## Is your feature request related to a problem? Please describe.
8 |
11 |
12 | ## Describe the solution you'd like
13 |
16 |
17 | ## Describe alternatives you've considered
18 |
21 |
22 | ## Additional context
23 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches: [develop]
6 | pull_request:
7 | branches: [master, develop]
8 |
9 | jobs:
10 | test:
11 | name: Test
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [12.x, 14.x]
17 |
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - name: Install dependencies
26 | run: npx ci
27 | - name: Build
28 | run: npm run build
29 | - name: Lint
30 | run: npm run lint
31 | - name: Test
32 | run: npm run test --if-present
33 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches: master
6 |
7 | jobs:
8 | release:
9 | name: Release
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: 14.x
19 | - name: Install dependencies
20 | run: npx ci
21 | - name: Build
22 | run: npm run build
23 | - name: Lint
24 | run: npm run lint
25 | - name: Test
26 | run: npm run test --if-present
27 | - name: Release
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | run: npx semantic-release
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | labels: 'bug: pending triage'
5 | ---
6 |
7 | ## Bug description
8 |
11 |
12 | ## Reproduction steps
13 |
20 |
21 | ## Environment
22 |
23 | - webpack-localize-asets-plugin version:
24 | - Webpack version:
25 | - Operating System:
26 | - Node version:
27 | - Package manager (npm/yarn/pnpm) and version:
28 |
--------------------------------------------------------------------------------
/tests/utils/rollup.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { rollup, RollupOptions } from 'rollup';
3 | import alias from '@rollup/plugin-alias';
4 | import vue from 'rollup-plugin-vue';
5 |
6 | const vueFragInstall = path.resolve(__dirname, '../../dist/install-vue-frag.js');
7 |
8 | const defaultConfig = (input: string) => ({
9 | input,
10 | plugins: [
11 | alias({
12 | entries: {
13 | 'vue-frag-plugin/install-vue-frag': vueFragInstall,
14 | },
15 | }),
16 | vue(),
17 | ],
18 | });
19 |
20 | type Config = RollupOptions & ReturnType;
21 |
22 | export async function build(
23 | input: string,
24 | configure?: (config: Config) => void,
25 | ) {
26 | const config: Config = defaultConfig(input);
27 |
28 | if (typeof configure === 'function') {
29 | configure(config);
30 | }
31 |
32 | const bundle = await rollup(config);
33 | const { output } = await bundle.generate({
34 | format: 'cjs',
35 | exports: 'default',
36 | });
37 | return output[0].code;
38 | }
39 |
--------------------------------------------------------------------------------
/tests/fixtures/index.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 |
4 | export const singleRootComponentPath = path.resolve(__dirname, './SingleRoot.vue');
5 | export const singleRootComponent = fs.readFileSync(singleRootComponentPath, 'utf-8');
6 |
7 | export const multiRootComponentPath = path.resolve(__dirname, './MultiRoot.vue');
8 | export const multiRootComponent = fs.readFileSync(multiRootComponentPath, 'utf-8');
9 |
10 | export const multiRootComponentWithScriptPath = path.resolve(__dirname, './MultiRootWithScript.vue');
11 | export const multiRootComponentWithScript = fs.readFileSync(multiRootComponentWithScriptPath, 'utf-8');
12 |
13 | export const multiRootComponentWithEmptyScriptPath = path.resolve(__dirname, './MultiRootWithEmptyScript.vue');
14 | export const multiRootComponentWithEmptyScript = fs.readFileSync(multiRootComponentWithEmptyScriptPath, 'utf-8');
15 |
16 | export const multiRootComponentWithTemplatePath = path.resolve(__dirname, './MultiRootWithTemplate.vue');
17 | export const multiRootComponentWithTemplate = fs.readFileSync(multiRootComponentWithTemplatePath, 'utf-8');
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Hiroki Osame
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'rollup';
2 | import resolve from '@rollup/plugin-node-resolve';
3 | import commonjs from '@rollup/plugin-commonjs';
4 | import dts from 'rollup-plugin-dts';
5 | import esbuild from 'rollup-plugin-esbuild';
6 | import { dependencies } from './package.json';
7 |
8 | export default [
9 | // Loader and Plugin
10 | defineConfig({
11 | input: {
12 | index: './src/index.ts',
13 | loader: './src/loader.ts',
14 | plugin: './src/plugin.ts',
15 | },
16 |
17 | plugins: [
18 | resolve(),
19 | commonjs(),
20 | esbuild({
21 | minify: true,
22 | }),
23 | ],
24 |
25 | external: [
26 | ...Object.keys(dependencies),
27 | ],
28 |
29 | output: {
30 | dir: './dist',
31 | format: 'commonjs',
32 | exports: 'auto',
33 | interop: false,
34 | },
35 | }),
36 |
37 | // Types
38 | defineConfig({
39 | input: './src/index.ts',
40 |
41 | plugins: [
42 | dts(),
43 | ],
44 |
45 | output: {
46 | dir: './dist',
47 | },
48 | }),
49 |
50 | // Vue frag auto installer
51 | defineConfig({
52 | input: './src/install-vue-frag.js',
53 |
54 | plugins: [
55 | resolve(),
56 | commonjs(),
57 | esbuild({
58 | minify: true,
59 | }),
60 | ],
61 |
62 | external: ['vue', 'vue-frag'],
63 |
64 | output: {
65 | dir: './dist',
66 | format: 'esm',
67 | },
68 | }),
69 | ];
70 |
--------------------------------------------------------------------------------
/tests/webpack-loader.spec.ts:
--------------------------------------------------------------------------------
1 | import { build } from 'webpack-test-utils';
2 | import { VueLoaderPlugin } from 'vue-loader';
3 | import { setupVueFragPlugin } from './utils/webpack';
4 | import { multiRootComponent } from './fixtures';
5 |
6 | const FragmentComponent = {
7 | '/src/index.js': 'export { default } from "./component.vue"',
8 | '/src/component.vue': multiRootComponent,
9 | };
10 |
11 | test('vue-loader complains about fragments w/o vue-frag/loader', async () => {
12 | const built = await build(FragmentComponent, (config) => {
13 | setupVueFragPlugin(config);
14 |
15 | config.module.rules.push({
16 | test: /\.vue$/,
17 | loader: 'vue-loader',
18 | });
19 |
20 | // @ts-expect-error VueLoaderPlugin uses Webpack 4 types
21 | config.plugins.push(new VueLoaderPlugin());
22 | });
23 |
24 | expect(built.stats.hasWarnings()).toBe(false);
25 | expect(built.stats.hasErrors()).toBe(true);
26 | });
27 |
28 | test('works with vue-loader', async () => {
29 | const built = await build(FragmentComponent, (config) => {
30 | setupVueFragPlugin(config);
31 |
32 | config.module.rules.push({
33 | test: /\.vue$/,
34 | use: [
35 | 'vue-loader',
36 | 'vue-frag-plugin/loader',
37 | ],
38 | });
39 |
40 | // @ts-expect-error VueLoaderPlugin uses Webpack 4 types
41 | config.plugins.push(new VueLoaderPlugin());
42 | });
43 |
44 | expect(built.stats.hasWarnings()).toBe(false);
45 | expect(built.stats.hasErrors()).toBe(false);
46 | expect(built.require('/dist')).toMatchObject({ _compiled: true });
47 | });
48 |
--------------------------------------------------------------------------------
/tests/transformer.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable unicorn/template-indent */
2 | import outdent from 'outdent';
3 | import transformer from '../src/transformer';
4 | import {
5 | singleRootComponent,
6 | multiRootComponent,
7 | multiRootComponentWithEmptyScript,
8 | multiRootComponentWithScript,
9 | multiRootComponentWithTemplate,
10 | } from './fixtures';
11 |
12 | test('ignore single root', () => {
13 | const transformed = transformer(singleRootComponent);
14 |
15 | expect(transformed.code).toBe(outdent`
16 |
17 |
18 | hello world
19 |
20 |
21 |
22 | `);
23 | });
24 |
25 | test('wraps multi root', () => {
26 | const transformed = transformer(multiRootComponent);
27 |
28 | expect(transformed.code).toBe(outdent`
29 |
30 |
31 | hello
32 |
33 |
34 | fragments
35 |
36 |
37 |
38 | `);
39 | });
40 |
41 | test('wraps multi root with empty script', () => {
42 | const transformed = transformer(multiRootComponentWithEmptyScript);
43 |
44 | expect(transformed.code).toBe(outdent`
45 |
46 |
47 | hello
48 |
49 |
50 | fragments
51 |
52 |
53 |
54 |
55 |
56 | `);
57 | });
58 |
59 | test('wraps multi root with script', () => {
60 | const transformed = transformer(multiRootComponentWithScript);
61 |
62 | expect(transformed.code).toBe(outdent`
63 |
64 |
65 | hello
66 |
67 |
68 | fragments
69 |
70 |
71 |
72 |
75 |
76 | `);
77 | });
78 |
79 | test('wraps multi root with script', () => {
80 | const transformed = transformer(multiRootComponentWithTemplate);
81 |
82 | expect(transformed.code).toBe(outdent`
83 |
84 |
85 |
86 | hello
87 |
88 |
89 | fragments
90 |
91 |
92 |
93 |
94 | `);
95 | });
96 |
97 | /* eslint-enable unicorn/template-indent */
98 |
--------------------------------------------------------------------------------
/src/transformer.ts:
--------------------------------------------------------------------------------
1 | import { parse } from '@vue/compiler-dom';
2 | import type { ElementNode } from '@vue/compiler-dom';
3 | import MagicString, { SourceMap } from 'magic-string';
4 | import { isElement } from 'vue-ast-utils';
5 |
6 | type Transform = {
7 | code: string;
8 | map?: SourceMap;
9 | };
10 |
11 | const importStatement = 'import "vue-frag-plugin/install-vue-frag";';
12 |
13 | function wrapVueFrag(
14 | magicString: MagicString,
15 | template: ElementNode,
16 | ) {
17 | const firstChild = template.children[0];
18 | const lastChild = template.children[template.children.length - 1];
19 |
20 | magicString.appendRight(firstChild.loc.start.offset, '');
21 | magicString.appendRight(lastChild.loc.end.offset, '
');
22 | }
23 |
24 | function injectVueFragImport(
25 | magicString: MagicString,
26 | template: ElementNode,
27 | script?: ElementNode,
28 | ) {
29 | if (!script) {
30 | magicString.appendRight(template.loc.end.offset, ``);
31 | return;
32 | }
33 |
34 | const hasChild = script.children[0];
35 | if (hasChild) {
36 | magicString.appendLeft(hasChild.loc.start.offset, importStatement);
37 | } else {
38 | const { source } = script.loc;
39 | const position = source.indexOf('') + 1;
40 | magicString.prependRight(script.loc.end.offset - position, importStatement);
41 | }
42 | }
43 |
44 | export default function transform(
45 | code: string,
46 | filename?: string,
47 | ): Transform {
48 | const result: Transform = { code };
49 |
50 | // Test invalid sfc
51 | const parsed = parse(code);
52 | const template = parsed.children.filter(isElement).find(child => child.tag === 'template');
53 |
54 | if (!template || template.children.length === 0) {
55 | return result;
56 | }
57 |
58 | const [rootElement] = template.children;
59 | const isRootTemplate = (
60 | isElement(rootElement)
61 | && rootElement.tag === 'template'
62 | );
63 |
64 | if (isRootTemplate || template.children.length > 1) {
65 | const magicString = new MagicString(code);
66 |
67 | wrapVueFrag(magicString, template);
68 |
69 | const script = parsed.children.filter(isElement).find(child => child.tag === 'script');
70 | injectVueFragImport(magicString, template, script);
71 |
72 | result.map = magicString.generateMap({
73 | source: filename,
74 | file: `${filename}.map`,
75 | });
76 |
77 | result.code = magicString.toString();
78 | }
79 |
80 | return result;
81 | }
82 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-frag-plugin",
3 | "version": "0.0.0-semantic-release",
4 | "description": "Webpack/Rollup/Vite plugin to use multiple root nodes in Vue 2",
5 | "keywords": [
6 | "vue-frag",
7 | "vue",
8 | "fragments",
9 | "rollup",
10 | "webpack",
11 | "vite"
12 | ],
13 | "license": "MIT",
14 | "repository": "privatenumber/vue-frag-plugin",
15 | "funding": "https://github.com/privatenumber/vue-frag-plugin?sponsor=1",
16 | "author": {
17 | "name": "Hiroki Osame",
18 | "email": "hiroki.osame@gmail.com"
19 | },
20 | "files": [
21 | "dist"
22 | ],
23 | "exports": {
24 | ".": {
25 | "require": "./dist/index.js"
26 | },
27 | "./loader": {
28 | "require": "./dist/loader.js"
29 | },
30 | "./install-vue-frag": {
31 | "import": "./dist/install-vue-frag.js"
32 | }
33 | },
34 | "types": "./dist/index.d.ts",
35 | "scripts": {
36 | "build": "rm -rf dist && rollup -c rollup.config.js",
37 | "test": "jest",
38 | "lint": "eslint ."
39 | },
40 | "husky": {
41 | "hooks": {
42 | "pre-commit": "lint-staged"
43 | }
44 | },
45 | "lint-staged": {
46 | "*.{js,ts}": [
47 | "eslint",
48 | "jest --bail --findRelatedTests"
49 | ]
50 | },
51 | "peerDependencies": {
52 | "vue-frag": "*"
53 | },
54 | "dependencies": {
55 | "@rollup/pluginutils": "^4.1.1",
56 | "@vue/compiler-dom": "^3.2.20",
57 | "magic-string": "^0.25.7"
58 | },
59 | "devDependencies": {
60 | "@pvtnbr/eslint-config": "^0.4.1",
61 | "@rollup/plugin-alias": "^3.1.8",
62 | "@rollup/plugin-commonjs": "^21.0.1",
63 | "@rollup/plugin-node-resolve": "^13.0.6",
64 | "@types/jest": "^27.0.2",
65 | "@types/node": "^16.11.6",
66 | "aggregate-error": "^4.0.0",
67 | "es-jest": "^1.4.1",
68 | "eslint": "^8.1.0",
69 | "esno": "^0.10.1",
70 | "fs-require": "^1.4.0",
71 | "husky": "^4.3.8",
72 | "jest": "^27.3.1",
73 | "memfs": "^3.3.0",
74 | "lint-staged": "^11.1.2",
75 | "outdent": "^0.8.0",
76 | "rollup": "^2.59.0",
77 | "rollup-plugin-dts": "^4.0.0",
78 | "rollup-plugin-esbuild": "^4.6.0",
79 | "rollup-plugin-vue": "^5.1.9",
80 | "typescript": "^4.4.4",
81 | "unionfs": "^4.4.0",
82 | "vue": "^2.6.14",
83 | "vue-ast-utils": "^1.0.1",
84 | "vue-frag": "^1.3.1",
85 | "vue-loader": "^15.9.8",
86 | "vue-template-compiler": "^2.6.14",
87 | "webpack": "^5.61.0",
88 | "webpack-test-utils": "^1.0.0"
89 | },
90 | "eslintConfig": {
91 | "extends": "@pvtnbr",
92 | "ignorePatterns": [
93 | "fixtures",
94 | "dist"
95 | ],
96 | "overrides": [
97 | {
98 | "files": "**/examples/**",
99 | "rules": {
100 | "import/no-unresolved": [
101 | 2,
102 | {
103 | "ignore": [
104 | "vue-frag-plugin"
105 | ]
106 | }
107 | ]
108 | }
109 | }
110 | ]
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-frag-plugin
2 |
3 | Webpack/Rollup/Vite plugin to use multiple root nodes in Vue 2 [Single-file Components (SFCs)](https://vuejs.org/v2/guide/single-file-components.html). Powered by [`vue-frag`](https://github.com/privatenumber/vue-frag).
4 |
5 |
6 | ```vue
7 |
8 |
9 |
10 | Hello
11 |
12 |
13 | Multiple root nodes
14 |
15 |
16 | ```
17 |
18 | Support this project by ⭐️ starring and sharing it. [Follow me](https://github.com/privatenumber) to see what other cool projects I'm working on! ❤️
19 |
20 | ## 🚀 Install
21 | ```sh
22 | npm i -D vue-frag-plugin vue-frag
23 | ```
24 |
25 | ## 🙋♂️ Why?
26 | [`vue-frag`](https://github.com/privatenumber/vue-frag) is a directive that lets you use Fragments in Vue.js 2 components, but you have to manually register it and insert it as the root node.
27 |
28 | `vue-frag-plugin` is a build-time plugin that automates this process, injecting vue-frag where necessary. You will be able to use multiple root nodes seamlessly in your SFCs, bringing the developer experience much closer to Vue 3.
29 |
30 | ## 🚦 Quick setup
31 |
32 | ### Webpack
33 | Add `vue-frag-plugin/loader` before `vue-loader` in `webpack.config.js`.
34 |
35 |
36 | Example webpack.config.js
37 |
38 |
39 | ```diff
40 | module.exports = {
41 | ...,
42 |
43 | module: {
44 | rules: [
45 | ...,
46 |
47 | // Update the vue-loader rule to insert `vue-frag-plugin/loader` before it
48 | {
49 | test: /\.vue$/,
50 | - loader: 'vue-loader',
51 | + use: [
52 | + 'vue-loader',
53 | + 'vue-frag-plugin/loader'
54 | + ]
55 | }
56 | ]
57 | }
58 | }
59 | ```
60 |
61 |
62 |
63 | ### Rollup / Vite
64 | 1. Import `vueFrag` from `vue-frag-plugin`
65 | 2. Add it to `plugins` before the Vue plugin in `rollup.config.js` or `vite.config.js`
66 |
67 |
68 | Example rollup.config.js
69 |
70 |
71 | ```diff
72 | import { definePlugin } from 'rollup
73 | import vue from 'rollup-plugin-vue'
74 | + import { vueFrag } from 'vue-frag-plugin'
75 |
76 | export default definePlugin({
77 | ...,
78 |
79 | plugins: [
80 | + vueFrag(), // Important this goes before `vue()`
81 | vue()
82 | ],
83 |
84 | ...
85 | })
86 | ```
87 |
88 |
89 |
90 | Example vite.config.js
91 |
92 |
93 | ```diff
94 | import { definePlugin } from 'vite'
95 | import { createVuePlugin } from 'vite-plugin-vue2'
96 | + import { vueFrag } from 'vue-frag-plugin'
97 |
98 | export default definePlugin({
99 | ...,
100 |
101 | plugins: [
102 | + vueFrag(), // Important this goes before `createVuePlugin()`
103 | createVuePlugin()
104 | ],
105 |
106 | ...
107 | })
108 | ```
109 |
110 |
111 | ## 💞 Related
112 | - [unplugin-vue2-script-setup](https://github.com/antfu/unplugin-vue2-script-setup) - Build-time plugin to use `