├── src ├── styles │ ├── colors.scss │ └── bootstrap.scss ├── locales │ ├── en-US │ │ └── common.js │ └── zh-TW │ │ └── common.js ├── router │ └── index.js ├── views │ ├── About.vue │ └── Home.vue ├── client-entry.js ├── index.template.html ├── components │ ├── LanguagePicker.vue │ └── Spinner.vue ├── filters │ └── index.js ├── App.vue ├── app.js ├── server-entry.js ├── store │ └── index.js └── services │ └── i18n.js ├── .gitignore ├── public └── logo.png ├── .babelrc ├── .editorconfig ├── manifest.json ├── .eslintrc.js ├── README.md ├── package.json └── server.js /src/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $color-text: #333; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | .idea 6 | *.iml -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertchan/vue-ssr-boilerplate/HEAD/public/logo.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }], 4 | "stage-2" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/locales/en-US/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'nav': { 3 | 'about': 'About', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/zh-TW/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'nav': { 3 | 'about': '關於', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Home from '../views/Home.vue'; 4 | import About from '../views/About.vue'; 5 | 6 | Vue.use(Router); 7 | 8 | export default new Router({ 9 | mode: 'history', 10 | scrollBehavior: () => ({ y: 0 }), 11 | routes: [ 12 | { path: '/', component: Home }, 13 | { path: '/about', component: About }, 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /src/client-entry.js: -------------------------------------------------------------------------------- 1 | import 'es6-promise/auto'; 2 | import { app, store } from './app'; 3 | // require('style-loader!css-loader!sass-loader./styles/bootstrap.scss'); 4 | 5 | // prime the store with server-initialized state. 6 | // the state is determined during SSR and inlined in the page markup. 7 | store.replaceState(window.__INITIAL_STATE__); // eslint-disable-line no-underscore-dangle 8 | 9 | // actually mount to DOM 10 | app.$mount('#app'); 11 | 12 | // service worker 13 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 14 | navigator.serviceWorker.register('/service-worker.js'); 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.js] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.json] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | [*.{sass,scss}] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | [*.html] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | [*.{diff,md}] 32 | trim_trailing_whitespace = false 33 | -------------------------------------------------------------------------------- /src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue SSR Boilerplate 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/LanguagePicker.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue Hackernews 2.0", 3 | "short_name": "Vue HN", 4 | "icons": [{ 5 | "src": "/public/logo-120.png", 6 | "sizes": "120x120", 7 | "type": "image/png" 8 | }, { 9 | "src": "/public/logo-144.png", 10 | "sizes": "144x144", 11 | "type": "image/png" 12 | }, { 13 | "src": "/public/logo-152.png", 14 | "sizes": "152x152", 15 | "type": "image/png" 16 | }, { 17 | "src": "/public/logo-192.png", 18 | "sizes": "192x192", 19 | "type": "image/png" 20 | },{ 21 | "src": "/public/logo-384.png", 22 | "sizes": "384x384", 23 | "type": "image/png" 24 | }], 25 | "start_url": "/", 26 | "background_color": "#f2f3f5", 27 | "display": "standalone", 28 | "theme_color": "#f60" 29 | } 30 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-bitwise: ["error", { "allow": ["~"] }] */ 2 | 3 | export function host(url) { 4 | const hostname = url.replace(/^https?:\/\//, '').replace(/\/.*$/, ''); 5 | const parts = hostname.split('.').slice(-3); 6 | if (parts[0] === 'www') parts.shift(); 7 | return parts.join('.'); 8 | } 9 | 10 | function pluralize(time, label) { 11 | if (time === 1) { 12 | return `${time}${label}`; 13 | } 14 | return `${time}${label}s`; 15 | } 16 | 17 | export function timeAgo(time) { 18 | const between = (Date.now() / 1000) - Number(time); 19 | if (between < 3600) { 20 | return pluralize(~~(between / 60), ' minute'); 21 | } else if (between < 86400) { 22 | return pluralize(~~(between / 3600), ' hour'); 23 | } 24 | return pluralize(~~(between / 86400), ' day'); 25 | } 26 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | extends: 'airbnb-base', 8 | // required to lint *.vue files 9 | plugins: [ 10 | 'html' 11 | ], 12 | // check if imports actually resolve 13 | settings: { 14 | 'import/resolver': { 15 | 'webpack': { 16 | 'config': 'build/webpack.base.conf.js' 17 | } 18 | } 19 | }, 20 | // add your custom rules here 21 | rules: { 22 | // don't require .vue extension when importing 23 | 'import/extensions': ['error', 'always', { 24 | 'js': 'never', 25 | 'vue': 'never' 26 | }], 27 | // allow debugger during development 28 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 29 | 'quote-props': ['error', 'consistent'], 30 | }, 31 | globals: { 32 | '__CLIENT__': true, 33 | '__SERVER__': true, 34 | 'navigator': true, 35 | 'window': true, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-ssr-boilerplate 2 | 3 | A stripped down version of [vue-hackernews-2](https://github.com/vuejs/vue-hackernews-2.0) 4 | for use as a boilerplate. 5 | 6 | ## Features 7 | 8 | - Server Side Rendering 9 | - Vue + vue-router + vuex working together 10 | - Server-side data pre-fetching 11 | - Client-side state & DOM hydration 12 | - Single-file Vue Components 13 | - Hot-reload in development 14 | - CSS extraction for production 15 | - [bootstrap-sass](https://github.com/twbs/bootstrap-sass) 16 | - [ESLint](http://eslint.org/) with Airbnb's base JS 17 | - [i18next](http://i18next.com/) internationalization 18 | 19 | ## Architecture Overview 20 | 21 | screen shot 2016-08-11 at 6 06 57 pm 22 | 23 | ## Build Setup 24 | 25 | **Requires Node.js 6+** 26 | 27 | ``` bash 28 | # install dependencies 29 | npm install 30 | 31 | # serve in dev mode, with hot reload at localhost:8080 32 | npm run dev 33 | 34 | # build for production 35 | npm run build 36 | 37 | # serve in production mode 38 | npm start 39 | ``` 40 | -------------------------------------------------------------------------------- /src/components/Spinner.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 54 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 47 | 48 | 50 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 37 | 38 | 52 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { sync } from 'vuex-router-sync'; 3 | import App from './App.vue'; 4 | import router from './router'; 5 | import store from './store'; 6 | import * as filters from './filters'; 7 | import I18N from './services/i18n'; 8 | 9 | // sync the router with the vuex store. 10 | // this registers `store.state.route` 11 | sync(store, router); 12 | 13 | // Load i18n service 14 | const i18nService = I18N.getInstance(); 15 | i18nService.init(['en-US', 'zh-TW'], 'en-US'); 16 | 17 | // register global utility filters. 18 | Object.keys(filters).forEach((key) => { 19 | Vue.filter(key, filters[key]); 20 | }); 21 | 22 | // create the app instance. 23 | // here we inject the router and store to all child components, 24 | // making them available everywhere as `this.$router` and `this.$store`. 25 | const app = new Vue(Vue.util.extend({ 26 | router, 27 | store, 28 | 29 | // Inject methods for all child components 30 | methods: { 31 | translate(key, options) { 32 | const activeLocale = this.$store.getters.activeLocale; 33 | return i18nService.translate(key, { locale: activeLocale, ...options }); 34 | }, 35 | }, 36 | }, App)); 37 | 38 | // expose the app, the router and the store. 39 | // note we are not mounting the app here, since bootstrapping will be 40 | // different depending on whether we are in a browser or on the server. 41 | export { app, router, store }; 42 | -------------------------------------------------------------------------------- /src/server-entry.js: -------------------------------------------------------------------------------- 1 | import { app, router, store } from './app'; 2 | 3 | const isDev = process.env.NODE_ENV !== 'production'; 4 | 5 | // This exported function will be called by `bundleRenderer`. 6 | // This is where we perform data-prefetching to determine the 7 | // state of our application before actually rendering it. 8 | // Since data fetching is async, this function is expected to 9 | // return a Promise that resolves to the app instance. 10 | 11 | /* eslint-disable no-param-reassign */ 12 | export default (context) => { 13 | const s = isDev && Date.now(); 14 | 15 | // set router's location 16 | router.push(context.url); 17 | const matchedComponents = router.getMatchedComponents(); 18 | 19 | // no matched routes 20 | if (!matchedComponents.length) { 21 | return Promise.reject({ code: '404' }); 22 | } 23 | 24 | // Call preFetch hooks on components matched by the route. 25 | // A preFetch hook dispatches a store action and returns a Promise, 26 | // which is resolved when the action is complete and store state has been 27 | // updated. 28 | return Promise.all(matchedComponents.map((component) => { 29 | if (component.preFetch) { 30 | return component.preFetch(store); 31 | } 32 | return {}; 33 | })).then(() => { 34 | /* eslint-disable no-console */ 35 | if (isDev) console.log(`data pre-fetch: ${Date.now() - s}ms`); 36 | /* eslint-disable no-console */ 37 | 38 | // After all preFetch hooks are resolved, our store is now 39 | // filled with the state needed to render the app. 40 | // Expose the state on the render context, and let the request handler 41 | // inline the state in the HTML response. This allows the client-side 42 | // store to pick-up the server-side state without having to duplicate 43 | // the initial data fetching on the client. 44 | context.initialState = store.state; 45 | return app; 46 | }); 47 | }; 48 | /* eslint-enable no-param-reassign */ 49 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | const store = new Vuex.Store({ 7 | state: { 8 | settings: { 9 | locale: 'en-US', 10 | view: { 11 | pageSize: 10, 12 | }, 13 | }, 14 | items: [], 15 | }, 16 | 17 | actions: { 18 | SET_LOCALE(context, locale) { 19 | context.commit('SET_LOCALE', locale); 20 | }, 21 | 22 | LOAD_ACTIVE_ITEMS(context) { 23 | const { commit } = context; 24 | 25 | // mock API response 26 | const collection = [ 27 | { 28 | title: 'Item 1', 29 | description: 'Description for item 1', 30 | }, 31 | { 32 | title: 'Item 2', 33 | description: 'Description for item 2', 34 | }, 35 | { 36 | title: 'Item 3', 37 | description: 'Description for item 3', 38 | }, 39 | { 40 | title: 'Item 4', 41 | description: 'Description for item 4', 42 | }, 43 | { 44 | title: 'Item 5', 45 | description: 'Description for item 5', 46 | }, 47 | ]; 48 | 49 | commit('SET_ACTIVE_ITEMS', collection); 50 | 51 | return collection; 52 | }, 53 | }, 54 | 55 | getters: { 56 | activeItems(state) { 57 | if (state.items !== null) { 58 | return state.items; 59 | } 60 | return []; 61 | }, 62 | 63 | activeLocale(state) { 64 | return state.settings.locale; 65 | }, 66 | 67 | viewSettings(state) { 68 | return state.settings.view; 69 | }, 70 | }, 71 | 72 | /* eslint-disable no-param-reassign */ 73 | mutations: { 74 | SET_LOCALE(state, locale) { 75 | state.settings.locale = locale; 76 | }, 77 | 78 | SET_ACTIVE_ITEMS(state, collection) { 79 | state.items = collection; 80 | }, 81 | }, 82 | /* eslint-enable no-param-reassign */ 83 | }); 84 | 85 | export default store; 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-ssr-boilderplate", 3 | "description": "A Vue.js project", 4 | "author": "Albert Chan", 5 | "private": true, 6 | "scripts": { 7 | "dev": "node server", 8 | "start": "cross-env NODE_ENV=production node server", 9 | "build": "rimraf dist && npm run build:client && npm run build:server", 10 | "build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js --progress --hide-modules", 11 | "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js --progress --hide-modules", 12 | "lint": "eslint --ext .js,.vue src test/unit/specs" 13 | }, 14 | "engines": { 15 | "node": ">=6.0", 16 | "npm": ">=3.0" 17 | }, 18 | "dependencies": { 19 | "compression": "^1.6.2", 20 | "es6-promise": "^4.0.5", 21 | "express": "^4.14.0", 22 | "html-webpack-plugin": "^2.24.1", 23 | "i18next": "^4.1.0", 24 | "lodash": "^4.17.0", 25 | "lru-cache": "^4.0.1", 26 | "serialize-javascript": "^1.3.0", 27 | "serve-favicon": "^2.3.0", 28 | "vue": "^2.0.0", 29 | "vue-router": "^2.0.0", 30 | "vue-server-renderer": "^2.0.0", 31 | "vuex": "^2.0.0", 32 | "vuex-router-sync": "^3.0.0" 33 | }, 34 | "devDependencies": { 35 | "autoprefixer": "^6.4.0", 36 | "babel-eslint": "^7.0.0", 37 | "bootstrap-sass": "~3.3.0", 38 | "buble": "^0.14.2", 39 | "buble-loader": "^0.3.2", 40 | "cross-env": "^3.1.3", 41 | "css-loader": "^0.25.0", 42 | "extract-text-webpack-plugin": "^2.0.0-beta.3", 43 | "eslint": "^3.7.1", 44 | "eslint-config-airbnb-base": "^8.0.0", 45 | "eslint-plugin-html": "^1.3.0", 46 | "eslint-plugin-import": "^1.16.0", 47 | "file-loader": "^0.9.0", 48 | "node-sass": "~3.13.0", 49 | "rimraf": "^2.5.4", 50 | "sass-loader": "~4.0.0", 51 | "style-loader": "~0.13.0", 52 | "stylus": "^0.54.5", 53 | "stylus-loader": "^2.1.2", 54 | "sw-precache-webpack-plugin": "^0.6.0", 55 | "url-loader": "^0.5.7", 56 | "vue-loader": "^9.9.3", 57 | "webpack": "^2.1.0-beta.26", 58 | "webpack-dev-middleware": "^1.6.1", 59 | "webpack-hot-middleware": "^2.12.2" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/services/i18n.js: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | 3 | let instance = null; 4 | 5 | export default class I18N { 6 | constructor() { 7 | if (!instance) { 8 | instance = this; 9 | } 10 | 11 | // Maintains the default locale from init 12 | this.defaultLocale = null; 13 | 14 | // Test whether instance is isInitialized 15 | this.isInitialized = false; 16 | 17 | // Maintains the supported locales that will be loaded 18 | this.registeredLocales = []; 19 | 20 | // Maintains the loaded implementation for i18n logic 21 | this.i18n = null; 22 | 23 | return instance; 24 | } 25 | 26 | static getInstance() { 27 | return new I18N(); 28 | } 29 | 30 | translate(translationKey, overrides = {}) { 31 | let value = translationKey; 32 | 33 | /* eslint-disable no-param-reassign */ 34 | overrides.lng = overrides.locale; 35 | /* eslint-enable no-param-reassign */ 36 | 37 | if (this.i18n && translationKey) { 38 | value = this.i18n.t(translationKey, overrides); 39 | } 40 | 41 | return value; 42 | } 43 | 44 | init(allLocales, defaultLocale) { 45 | if (!this.isInitialized) { 46 | // Save provided settings 47 | this.registeredLocales = allLocales; 48 | this.defaultLocale = defaultLocale; 49 | 50 | // Build resource bundle of translations 51 | const resources = instance.loadAllTranslations(); 52 | 53 | i18next.init({ 54 | initImmediate: false, 55 | 56 | lng: this.defaultLocale, 57 | 58 | preload: this.registeredLocales, 59 | 60 | defaultNS: 'common', 61 | 62 | interpolation: { 63 | escapeValue: false, 64 | }, 65 | 66 | resources, 67 | }); 68 | 69 | // Add i18n implementation to service 70 | this.i18n = i18next; 71 | 72 | // Mark as initialized 73 | this.isInitialized = true; 74 | } 75 | } 76 | 77 | getResourceBundle() { 78 | let bundle = null; 79 | if (this.i18n && this.i18n.store) { 80 | bundle = this.i18n.store.data; 81 | } 82 | return bundle; 83 | } 84 | 85 | loadAllTranslations() { 86 | const resources = {}; 87 | 88 | if (this.registeredLocales && this.registeredLocales.length > 0) { 89 | this.registeredLocales.map((locale) => { 90 | // Initialize tree for locale 91 | resources[locale] = {}; 92 | 93 | /* eslint-disable global-require, import/no-dynamic-require */ 94 | const common = require(`../locales/${locale}/common.js`); 95 | /* eslint-enable global-require, import/no-dynamic-require */ 96 | resources[locale].common = common || {}; 97 | 98 | return resources[locale].common; 99 | }); 100 | } 101 | return resources; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | process.env.VUE_ENV = 'server'; 2 | const isProd = process.env.NODE_ENV === 'production'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const express = require('express'); 7 | const favicon = require('serve-favicon'); 8 | const compression = require('compression'); 9 | const serialize = require('serialize-javascript'); 10 | const resolve = file => path.resolve(__dirname, file); 11 | 12 | const app = express(); 13 | 14 | let indexHTML; // generated by html-webpack-plugin 15 | let renderer; // created from the webpack-generated server bundle 16 | if (isProd) { 17 | // in production: create server renderer and index HTML from real fs 18 | renderer = createRenderer(fs.readFileSync(resolve('./dist/server-bundle.js'), 'utf-8')); 19 | indexHTML = parseIndex(fs.readFileSync(resolve('./dist/index.html'), 'utf-8')); 20 | } else { 21 | // in development: setup the dev server with watch and hot-reload, 22 | // and update renderer / index HTML on file change. 23 | require('./build/setup-dev-server')(app, { 24 | bundleUpdated: bundle => { 25 | renderer = createRenderer(bundle); 26 | }, 27 | indexUpdated: index => { 28 | indexHTML = parseIndex(index); 29 | } 30 | }) 31 | } 32 | 33 | function createRenderer (bundle) { 34 | // https://github.com/vuejs/vue/blob/next/packages/vue-server-renderer/README.md#why-use-bundlerenderer 35 | return require('vue-server-renderer').createBundleRenderer(bundle, { 36 | cache: require('lru-cache')({ 37 | max: 1000, 38 | maxAge: 1000 * 60 * 15, 39 | }), 40 | }); 41 | } 42 | 43 | function parseIndex (template) { 44 | const contentMarker = '' 45 | const i = template.indexOf(contentMarker) 46 | return { 47 | head: template.slice(0, i), 48 | tail: template.slice(i + contentMarker.length) 49 | } 50 | } 51 | 52 | const serve = (path, cache) => express.static(resolve(path), { 53 | maxAge: cache && isProd ? 60 * 60 * 24 * 30 : 0 54 | }) 55 | 56 | app.use(compression({ threshold: 0 })) 57 | app.use(favicon('./public/logo.png')) 58 | app.use('/service-worker.js', serve('./dist/service-worker.js')) 59 | app.use('/manifest.json', serve('./manifest.json')) 60 | app.use('/dist', serve('./dist')) 61 | app.use('/public', serve('./public')) 62 | 63 | app.get('*', (req, res) => { 64 | if (!renderer) { 65 | return res.end('waiting for compilation... refresh in a moment.') 66 | } 67 | 68 | res.setHeader("Content-Type", "text/html"); 69 | var s = Date.now() 70 | const context = { url: req.url } 71 | const renderStream = renderer.renderToStream(context) 72 | 73 | renderStream.once('data', () => { 74 | res.write(indexHTML.head) 75 | }) 76 | 77 | renderStream.on('data', chunk => { 78 | res.write(chunk) 79 | }) 80 | 81 | renderStream.on('end', () => { 82 | // embed initial store state 83 | if (context.initialState) { 84 | res.write( 85 | `` 88 | ) 89 | } 90 | res.end(indexHTML.tail) 91 | console.log(`whole request: ${Date.now() - s}ms`) 92 | }) 93 | 94 | renderStream.on('error', err => { 95 | if (err && err.code === '404') { 96 | res.status(404).end('404 | Page Not Found') 97 | return 98 | } 99 | // Render Error Page or Redirect 100 | res.status(500).end('Internal Error 500') 101 | console.error(`error during render : ${req.url}`) 102 | console.error(err) 103 | }) 104 | }) 105 | 106 | const port = process.env.PORT || 8080 107 | app.listen(port, () => { 108 | console.log(`server started at localhost:${port}`) 109 | }) 110 | -------------------------------------------------------------------------------- /src/styles/bootstrap.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | // Core variables and mixins 8 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/variables"; 9 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/mixins"; 10 | 11 | // Reset and dependencies 12 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/normalize"; 13 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/print"; 14 | // @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/glyphicons"; 15 | 16 | // Core CSS 17 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; 18 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/type"; 19 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/code"; 20 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/grid"; 21 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tables"; 22 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/forms"; 23 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/buttons"; 24 | 25 | // Components 26 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/component-animations"; 27 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/dropdowns"; 28 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/button-groups"; 29 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/input-groups"; 30 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/navs"; 31 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/navbar"; 32 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs"; 33 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/pagination"; 34 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/pager"; 35 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/labels"; 36 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/badges"; 37 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/jumbotron"; 38 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/thumbnails"; 39 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/alerts"; 40 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/progress-bars"; 41 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/media"; 42 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/list-group"; 43 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/panels"; 44 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed"; 45 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/wells"; 46 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/close"; 47 | 48 | // Components w/ JavaScript 49 | // @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/modals"; 50 | // @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tooltip"; 51 | // @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/popovers"; 52 | // @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/carousel"; 53 | 54 | // Utility classes 55 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/utilities"; 56 | @import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities"; 57 | --------------------------------------------------------------------------------