├── .gitignore ├── README.md ├── gulpfile.coffee ├── package.json ├── src ├── comp │ ├── container.coffee │ ├── counter.coffee │ ├── home.coffee │ ├── page.coffee │ ├── panel.coffee │ └── piece.vue ├── main.coffee ├── main.css ├── router.coffee └── store.coffee ├── tasks ├── settings.coffee └── template.coffee ├── webpack-build.config.coffee ├── webpack.config.coffee └── webpack.server.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | /node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Vue CoffeeScript Workflow 3 | ---- 4 | 5 | > Templating is tricky, use language based DSL. 6 | 7 | This project demonstrates writing Vue.js 2 in plain CoffeeScript. 8 | The goal is to find an easy solution to render HTML during building. 9 | 10 | ### Usage 11 | 12 | * Develop 13 | 14 | ```bash 15 | gulp ssr 16 | webpack-dev-server --hot --inline 17 | ``` 18 | 19 | Open `build/index.html` to debug. 20 | 21 | There are actually 3 entries, and I'm trying to make it isomorphic: 22 | 23 | ```text 24 | build/index.html 25 | build/page/a.html 26 | build/page/b.html 27 | ``` 28 | 29 | * Build for production 30 | 31 | ```bash 32 | webpack --config webpack-build.config.coffee 33 | webpack --config webpack.server.coffee 34 | env=prod gulp ssr 35 | ``` 36 | 37 | ### TODO 38 | 39 | Still need to work on: 40 | 41 | * [x] build with revisions 42 | * [x] support `*.vue` files and build code 43 | * [ ] hot code swapping 44 | 45 | ### License 46 | 47 | MIT 48 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | gulp = require 'gulp' 4 | path = require 'path' 5 | 6 | settings = require './tasks/settings' 7 | 8 | gulp.task 'ssr', (cb) -> 9 | process.env.VUE_ENV = 'server' 10 | Vue = require 'vue' 11 | Vuex = require 'vuex' 12 | VueRouter = require 'vue-router' 13 | mkpath = require 'mkpath' 14 | # need to put it before store initialization 15 | Vue.use Vuex 16 | store = require './src/store' 17 | router = require './src/router' 18 | renderer = require('vue-server-renderer').createRenderer() 19 | # Container = require './src/comp/container' 20 | Container = require './build/container' 21 | template = require './tasks/template' 22 | Vue.use VueRouter 23 | 24 | # this is the initial address 25 | entries = [ 26 | 'index.html' 27 | 'page/a.html' 28 | 'page/b.html' 29 | ] 30 | entries.forEach (address) -> 31 | app = new Vue 32 | router: router 33 | store: store 34 | components: 35 | container: Container 36 | render: (h) -> 37 | h 'container' 38 | router.push address 39 | renderer.renderToString app, (err, appHtml) -> 40 | if err? 41 | throw err 42 | else 43 | html = template.render appHtml, store.state, settings 44 | htmlPath = path.join 'build', address 45 | console.log 'render entry:', htmlPath 46 | mkpath.sync path.dirname(htmlPath) 47 | fs.writeFileSync htmlPath, html 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-coffee-workflow", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "jiyinyiyong", 10 | "license": "MIT", 11 | "dependencies": { 12 | "gulp": "^3.9.1", 13 | "vue": "^2.0.3", 14 | "vue-router": "^2.0.1", 15 | "vuex": "^2.0.0" 16 | }, 17 | "devDependencies": { 18 | "coffee-loader": "^0.7.2", 19 | "coffee-script": "^1.11.1", 20 | "css-loader": "^0.25.0", 21 | "extract-text-webpack-plugin": "^1.0.1", 22 | "mkpath": "^1.0.0", 23 | "stir-template": "^0.1.6", 24 | "style-loader": "^0.13.1", 25 | "vue-loader": "^9.7.0", 26 | "vue-server-renderer": "^2.0.3", 27 | "webpack": "^1.13.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/comp/container.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vue = require 'vue' 3 | 4 | Panel = require './panel' 5 | Counter = require './counter' 6 | Piece = require './piece.vue' 7 | 8 | module.exports = Vue.component 'container', 9 | props: {} 10 | components: 11 | panel: Panel 12 | counter: Counter 13 | piece: Piece 14 | render: (h) -> 15 | h 'div', attrs: {id: 'app'}, [ 16 | h 'div', [ 17 | 'app!' 18 | ] 19 | h 'panel' 20 | h 'counter' 21 | h 'router-view', class: 'view' 22 | h 'piece' 23 | ] 24 | -------------------------------------------------------------------------------- /src/comp/counter.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vue = require 'vue' 3 | 4 | module.exports = Vue.component 'counter', 5 | computed: 6 | count: -> 7 | @$store.state.count 8 | methods: 9 | onClick: (event) -> 10 | console.log @$store.dispatch('inc') 11 | render: (h) -> 12 | h 'div', [ 13 | 'counter:' 14 | JSON.stringify(@count) 15 | h 'div', on: {click: @onClick}, [ 16 | 'add ' 17 | ] 18 | ] 19 | -------------------------------------------------------------------------------- /src/comp/home.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vue = require 'vue' 3 | 4 | module.exports = Vue.component 'home', 5 | props: {} 6 | render: (h) -> 7 | h 'div', attrs: {}, [ 8 | 'routed page home' 9 | ] 10 | -------------------------------------------------------------------------------- /src/comp/page.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vue = require 'vue' 3 | 4 | module.exports = Vue.component 'home', 5 | props: {} 6 | render: (h) -> 7 | h 'div', attrs: {}, [ 8 | "routed page #{JSON.stringify(this.$route.params)}" 9 | ] 10 | -------------------------------------------------------------------------------- /src/comp/panel.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vue = require 'vue' 3 | 4 | module.exports = Vue.component 'home', 5 | props: {} 6 | render: (h) -> 7 | h 'div', attrs: {}, [ 8 | h 'div', [ 9 | h 'router-link', props: {to: '/index.html'}, ['/index.html'] 10 | ] 11 | h 'div', [ 12 | h 'router-link', props: {to: '/page/a.html'}, ['/page/a.html'] 13 | ] 14 | h 'div', [ 15 | h 'router-link', props: {to: '/page/b.html'}, ['/page/b.html'] 16 | ] 17 | ] 18 | -------------------------------------------------------------------------------- /src/comp/piece.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | -------------------------------------------------------------------------------- /src/main.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vue = require 'vue' 3 | Vuex = require 'vuex' 4 | VueRouter = require 'vue-router' 5 | 6 | Vue.use VueRouter 7 | Vue.use Vuex 8 | 9 | Container = require './comp/container' 10 | router = require './router' 11 | store = require './store' 12 | 13 | storeEl = document.querySelector('#store') 14 | if storeEl? 15 | rawData = storeEl.getAttribute('content') 16 | store.replaceState JSON.parse(rawData) 17 | 18 | console.log 'store:', store 19 | 20 | new Vue 21 | el: '#app' 22 | router: router 23 | store: store 24 | components: 25 | container: Container 26 | render: (h) -> 27 | h 'container', {} 28 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: hsl(200,80%,90%); 4 | } 5 | -------------------------------------------------------------------------------- /src/router.coffee: -------------------------------------------------------------------------------- 1 | 2 | VueRouter = require 'vue-router' 3 | 4 | Home = require './comp/home' 5 | Page = require './comp/page' 6 | 7 | module.exports = new VueRouter 8 | mode: 'history' 9 | base: __dirname # what's this for? 10 | routes: [ 11 | path: '/index.html', component: Home 12 | , 13 | path: '/page/:x', component: Page 14 | ] 15 | -------------------------------------------------------------------------------- /src/store.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vuex = require 'vuex' 3 | 4 | state = count: 0 5 | 6 | mutations = 7 | increment: (state) -> 8 | state.count = state.count + 1 9 | decrement: (state) -> 10 | state.count = state.count - 1 11 | 12 | getters = 13 | count: (state) -> state.count 14 | 15 | actions = 16 | inc: ({commit}) -> 17 | commit 'increment' 18 | dec: ({commit}) -> 19 | commit 'increment' 20 | 21 | module.exports = new Vuex.Store {state, mutations, actions, getters} 22 | -------------------------------------------------------------------------------- /tasks/settings.coffee: -------------------------------------------------------------------------------- 1 | 2 | env = process.env.env 3 | 4 | module.exports = switch env 5 | when 'prod' 6 | assets = require '../build/assets.json' 7 | env: 'prod' 8 | mainJs: "/#{assets.main}" 9 | mainCss: "/#{assets.style[1]}" 10 | else 11 | env: 'dev' 12 | mainJs: 'http://localhost:8080/main.js' 13 | mainCss: 'http://localhost:8080/style.js' 14 | -------------------------------------------------------------------------------- /tasks/template.coffee: -------------------------------------------------------------------------------- 1 | 2 | stir = require 'stir-template' 3 | 4 | {html, body, head, meta, div, link, script} = stir 5 | 6 | exports.render = (appHtml, storeData, settings) -> 7 | storeContent = JSON.stringify(storeData).replace(/"/g, '"') 8 | stir.render stir.doctype(), 9 | html null, 10 | head null, 11 | script defer: true, src: settings.mainJs 12 | if settings.env is 'prod' 13 | link rel: 'stylesheet', href: settings.mainCss 14 | else 15 | script defer: true, src: settings.mainCss 16 | if storeData? 17 | meta id: 'store', content: storeContent 18 | body null, 19 | if appHtml? 20 | appHtml 21 | else 22 | div id: 'app' 23 | -------------------------------------------------------------------------------- /webpack-build.config.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | ExtractTextPlugin = require 'extract-text-webpack-plugin' 3 | 4 | module.exports = 5 | entry: 6 | style: './src/main.css' 7 | main: './src/main.coffee' 8 | output: 9 | path: 'build/' 10 | filename: '[name].[hash:8].js' 11 | module: 12 | loaders: [ 13 | test: /\.coffee$/, loader: 'coffee-loader' 14 | , 15 | test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') 16 | , 17 | test: /\.vue$/, loader: 'vue' 18 | ] 19 | resolve: 20 | extensions: ['', '.coffee', '.js'] 21 | plugins: [ 22 | new ExtractTextPlugin '[name].[hash:8].css' 23 | , 24 | -> 25 | @plugin "done", (stats) -> 26 | content = JSON.stringify stats.toJson().assetsByChunkName 27 | fs.writeFileSync 'build/assets.json', content 28 | ] 29 | -------------------------------------------------------------------------------- /webpack.config.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports = 3 | entry: 4 | style: './src/main.css' 5 | main: './src/main.coffee' 6 | output: 7 | filename: '[name].js' 8 | module: 9 | loaders: [ 10 | test: /\.coffee$/, loader: 'coffee-loader' 11 | , 12 | test: /\.css$/, loader: 'style!css' 13 | , 14 | test: /\.vue$/, loader: 'vue' 15 | ] 16 | resolve: 17 | extensions: ['', '.coffee', '.js'] 18 | -------------------------------------------------------------------------------- /webpack.server.coffee: -------------------------------------------------------------------------------- 1 | 2 | webpack = require 'webpack' 3 | 4 | module.exports = 5 | target: 'node' 6 | entry: 7 | container: './src/comp/container.coffee' 8 | output: 9 | path: 'build/' 10 | filename: 'container.js' 11 | libraryTarget: 'commonjs2' 12 | externals: Object.keys(require('./package.json').dependencies), 13 | module: 14 | loaders: [ 15 | test: /\.coffee$/, loader: 'coffee-loader' 16 | , 17 | test: /\.css$/, loader: 'style!css' 18 | , 19 | test: /\.vue$/, loader: 'vue' 20 | ] 21 | resolve: 22 | extensions: ['', '.coffee', '.js'] 23 | plugins: [] 24 | --------------------------------------------------------------------------------