Skip to content

davidivkovic/babel-vue-ftn

Repository files navigation

babel-vue-ftn

Transpiles a vue-cli project into a non-bundled, non node-dependent project.

Goals

The purpose of this tool is compiling away Single File Components (SFCs) whilst making the resulting code look like it was written by hand.

This tool was build to ease development of a Vue project during the course "Web programming" at the Faculty of Technical Sciences.

Our professor forbids using Node.js, so in order to use SFCs and standard vue tooling I developed this tool.

This project relies on

Examples

Transforming a .js file

Input:

src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './index.css'

const app = createApp(App)

app.use(router)
app.mount('#app')

Output:

dist/src/main.js

import { createApp } from '/modules/[email protected]'
import App from './App.js'
import router from './router/index.js'

const app = createApp(App)

app.use(router)
app.mount('#app')

dist/index.html

...
<head>
    <script type="module" src="src/main.js"></script>
</head>

<body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
</body>
...

Transforming a .vue file (SFC)

Note: All style tags will be extracted into separate .css files and eagerly injected into the index.html entrypoint as stylesheets:

<link rel="stylesheet" href="/src/Component.css">

Input:

src/Component.vue

<template>
  <div>
    <button
      @click="changeColor()"
      :style="{color: msg}"
    >
      {{ msg }}
    </button>
    <input 
      class="red"
      v-model="msg"
      :style="{color: msg}"
    />
    <AcademicCapIcon/>
    <CogIcon/>
    <CameraIcon/>
  </div>
</template>

<script>
import { ref, watch, onMounted } from 'vue'
import { AcademicCapIcon, CogIcon } from '@heroicons/vue/solid'
import { CameraIcon } from '@heroicons/vue/outline'

export default { 
  components: {
    AcademicCapIcon,
    CogIcon,
    CameraIcon
  },
  setup() {
    onMounted(() => console.log('Component Mounted'))

    const counter = ref(0)
    const msg = ref('red')
    
    const setMsg = value => msg.value = value
    const changeColor = () => (counter.value++ % 2 == 0) ? setMsg('yellow') : setMsg('red')
    watch(
      () => msg,
      () => console.log(`Color changed to ${msg.value}`)
    )

    return {
      msg,
      changeColor
    }
  }
}
</script>

<style>
.red {
  color:orange;
}
</style>

Output:

dist/src/Component.js

import { onMounted, ref, watch } from '/modules/[email protected]'
import CameraIcon from '/modules/@heroicons/vue/outline/CameraIcon.js'
import AcademicCapIcon from '/modules/@heroicons/vue/solid/AcademicCapIcon.js'
import CogIcon from '/modules/@heroicons/vue/solid/CogIcon.js'

export default {
  template: `  
    <div>
      <button
        @click="changeColor()"
        :style="{color: msg}"
      >
        {{ msg }}
      </button>
      <input 
        class="red"
        v-model="msg"
        :style="{color: msg}"
      />
      <AcademicCapIcon/>
      <CogIcon/>
      <CameraIcon/>
    </div>
  `,
  components: { AcademicCapIcon, CogIcon, CameraIcon },
  setup() {
    onMounted(() => console.log('Component Mounted'))

    const counter = ref(0)
    const msg = ref('red')

    const setMsg = value => (msg.value = value)
    const changeColor = () => counter.value++ % 2 == 0 ? setMsg('yellow') : setMsg('red')
    watch(
      () => msg,
      () => console.log(`Color changed to ${msg.value}`)
    )

    return {
      msg,
      changeColor
    }
  },
}

dist/Component.css

.red {
  color:orange;
}

Limitations

  • Not very modular
  • File type loaders have to be changed in code

Supports:

  • Only vue@next
  • Only SFC components with 'export default {}'
  • Lazily loading components using import()

Does NOT support:

  • Lazily injecting component styles on load
  • <script setup>
  • <style scoped>
  • bindings in <style>

Configuration

With no module resolution algorithms or bundling available, we have to specify module replacements manually.

You can get npm modules off of CDNs such as

Example configuration (place this file in your project root):

imports.config.js

/*
  Config for https://www.npmjs.com/package/babel-plugin-transform-imports
*/
const importOptions = {
  'vue': {
    transform: '/modules/[email protected]',
    skipDefaultConversion: true
  },
  '@vue/devtools-api': {
    transform: '/modules/[email protected]',
    skipDefaultConversion: true
  },
  'vue-router': {
    transform: '/modules/[email protected]',
    skipDefaultConversion: true
  },
  '@headlessui/vue': {
    transform: '/modules/[email protected]',
    skipDefaultConversion: true
  },
  '@heroicons/vue\/[^\/]*$': {
    transform: (importName, matches) => `/modules/${matches[0]}/${importName}.js`,
    skipDefaultConversion: false
  },
  'date-fns': {
    transform: '/modules/[email protected]',
    skipDefaultConversion: true
  }
}

/*
  Specify modules to be excluded from import transforms.
  These should be 'leaves' of the dependency graph.
  They should have no dependencies themselves.
  This is a performance optimization.
*/
const excludedLibraries = [
  '[email protected]',
  '[email protected]'
]

export {
  importOptions,
  excludedLibraries
}

Usage

Print usage:

>> node index.js --help

babel-transform-vue-ftn

  Transpiles a vue-cli project into a non-bundled, non node-dependent project.

Options

  -i, --input string   The root directory of the project to transpile.
  -o, --output string  The output directory where the built files will be placed.
                       The default is /dist under the project root.
  -h, --help           Print this usage guide.

Transpile a project:

>> node index.js --input E:\Projects\web

[ Building... ]
[ Build finished ]: 759.051ms

DONE Build complete. The dist directory E:/Projects/web/dist is ready to be deployed.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published