< Prev
< Prev
{{ page }}/{{ totalPages }}
@@ -19,20 +19,18 @@
import axios from 'axios'
export default {
- transition (to, from) {
+ transition(to, from) {
if (!from) return 'slide-left'
return +to.query.page < +from.query.page ? 'slide-right' : 'slide-left'
},
- asyncData ({ query }) {
+ async asyncData({ query }) {
const page = +query.page || 1
- return axios.get('https://reqres.in/api/users?page=' + page)
- .then((res) => {
- return {
- page: +res.data.page,
- totalPages: res.data.total_pages,
- users: res.data.data
- }
- })
+ const { data } = await axios.get(`https://reqres.in/api/users?page=${page}`)
+ return {
+ page: +data.page,
+ totalPages: data.total_pages,
+ users: data.data
+ }
}
}
diff --git a/examples/spa/assets/main.css b/examples/spa/assets/main.css
new file mode 100644
index 0000000000..1baea580c6
--- /dev/null
+++ b/examples/spa/assets/main.css
@@ -0,0 +1,6 @@
+.appear-active {
+ transition: opacity .5s
+}
+.appear {
+ opacity: 0
+}
diff --git a/examples/spa/nuxt.config.js b/examples/spa/nuxt.config.js
new file mode 100644
index 0000000000..7108b6c479
--- /dev/null
+++ b/examples/spa/nuxt.config.js
@@ -0,0 +1,41 @@
+module.exports = {
+ /*
+ ** Single Page Application mode
+ ** Means no SSR
+ */
+ mode: 'spa',
+ /*
+ ** Headers of the page (works with SPA!)
+ */
+ head: {
+ title: 'SPA mode with Nuxt.js',
+ meta: [
+ { charset: 'utf-8' },
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+ { hid: 'description', name: 'description', content: 'Single Page Application made with Nuxt.js' }
+ ],
+ link: [
+ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
+ ]
+ },
+ /*
+ ** Add css for appear transition
+ */
+ css: ['~/assets/main.css'],
+ /*
+ ** Customize loading indicator
+ */
+ loadingIndicator: {
+ /*
+ ** See https://nuxtjs.org/api/configuration-loading-indicator for available loading indicators
+ ** You can add a custom indicator by giving a path
+ */
+ // name: 'folding-cube',
+ /*
+ ** You can give custom options given to the template
+ ** See https://github.com/nuxt/nuxt.js/blob/dev/lib/app/views/loading/folding-cube.html
+ */
+ // color: '#DBE1EC'
+ // background: 'white'
+ }
+}
diff --git a/examples/spa/package.json b/examples/spa/package.json
new file mode 100644
index 0000000000..92325ecf81
--- /dev/null
+++ b/examples/spa/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "hello-nuxt",
+ "dependencies": {
+ "nuxt": "latest"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt"
+ }
+}
diff --git a/examples/spa/pages/about.vue b/examples/spa/pages/about.vue
new file mode 100644
index 0000000000..78419630fe
--- /dev/null
+++ b/examples/spa/pages/about.vue
@@ -0,0 +1,16 @@
+
+
+
Hi from {{ name }}
+
Home page
+
+
+
+
diff --git a/examples/spa/pages/index.vue b/examples/spa/pages/index.vue
new file mode 100644
index 0000000000..9655d7d866
--- /dev/null
+++ b/examples/spa/pages/index.vue
@@ -0,0 +1,6 @@
+
+
+
Welcome!
+ About page
+
+
diff --git a/examples/spa/static/favicon.ico b/examples/spa/static/favicon.ico
new file mode 100644
index 0000000000..382fecbbf9
Binary files /dev/null and b/examples/spa/static/favicon.ico differ
diff --git a/examples/static-images/pages/about.vue b/examples/static-images/pages/about.vue
index 713c44128f..9e4840f4bd 100644
--- a/examples/static-images/pages/about.vue
+++ b/examples/static-images/pages/about.vue
@@ -9,7 +9,7 @@
diff --git a/examples/tailwindcss/pages/index.vue b/examples/tailwindcss/pages/index.vue
new file mode 100644
index 0000000000..42b3f02ef1
--- /dev/null
+++ b/examples/tailwindcss/pages/index.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
The Coldest Sunset
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.
+
+
+ #photography
+ #travel
+ #winter
+
+
+
About
+
+
diff --git a/examples/tailwindcss/postcss.config.js b/examples/tailwindcss/postcss.config.js
new file mode 100644
index 0000000000..6b4212984e
--- /dev/null
+++ b/examples/tailwindcss/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: [
+ require('tailwindcss')('./tailwind.js'),
+ require('autoprefixer')
+ ]
+}
diff --git a/examples/tailwindcss/tailwind.js b/examples/tailwindcss/tailwind.js
new file mode 100644
index 0000000000..9a4af76aba
--- /dev/null
+++ b/examples/tailwindcss/tailwind.js
@@ -0,0 +1,744 @@
+/*
+
+Tailwind - The Utility-First CSS Framework
+
+A project by Adam Wathan (@adamwathan), Jonathan Reinink (@reinink),
+David Hemphill (@davidhemphill) and Steve Schoger (@steveschoger).
+
+Welcome to the Tailwind config file. This is where you can customize
+Tailwind specifically for your project. Don't be intimidated by the
+length of this file. It's really just a big JavaScript object and
+we've done our very best to explain each section.
+
+View the full documentation at https://tailwindcss.com.
+
+|-------------------------------------------------------------------------------
+| The default config
+|-------------------------------------------------------------------------------
+|
+| This variable contains the default Tailwind config. You don't have
+| to use it, but it can sometimes be helpful to have available. For
+| example, you may choose to merge your custom configuration
+| values with some of the Tailwind defaults.
+|
+*/
+
+// var defaultConfig = require('tailwindcss').defaultConfig()
+
+/*
+|-------------------------------------------------------------------------------
+| Colors https://tailwindcss.com/docs/colors
+|-------------------------------------------------------------------------------
+|
+| Here you can specify the colors used in your project. To get you started,
+| we've provided a generous palette of great looking colors that are perfect
+| for prototyping, but don't hesitate to change them for your project. You
+| own these colors, nothing will break if you change everything about them.
+|
+| We've used literal color names ("red", "blue", etc.) for the default
+| palette, but if you'd rather use functional names like "primary" and
+| "secondary", or even a numeric scale like "100" and "200", go for it.
+|
+*/
+
+var colors = {
+ 'transparent': 'transparent',
+
+ 'black': '#222b2f',
+ 'grey-darkest': '#364349',
+ 'grey-darker': '#596a73',
+ 'grey-dark': '#70818a',
+ 'grey': '#9babb4',
+ 'grey-light': '#dae4e9',
+ 'grey-lighter': '#f3f7f9',
+ 'grey-lightest': '#fafcfc',
+ 'white': '#ffffff',
+
+ 'red-darkest': '#420806',
+ 'red-darker': '#6a1b19',
+ 'red-dark': '#cc1f1a',
+ 'red': '#e3342f',
+ 'red-light': '#ef5753',
+ 'red-lighter': '#f9acaa',
+ 'red-lightest': '#fcebea',
+
+ 'orange-darkest': '#542605',
+ 'orange-darker': '#7f4012',
+ 'orange-dark': '#de751f',
+ 'orange': '#f6993f',
+ 'orange-light': '#faad63',
+ 'orange-lighter': '#fcd9b6',
+ 'orange-lightest': '#fff5eb',
+
+ 'yellow-darkest': '#453411',
+ 'yellow-darker': '#684f1d',
+ 'yellow-dark': '#f2d024',
+ 'yellow': '#ffed4a',
+ 'yellow-light': '#fff382',
+ 'yellow-lighter': '#fff9c2',
+ 'yellow-lightest': '#fcfbeb',
+
+ 'green-darkest': '#032d19',
+ 'green-darker': '#0b4228',
+ 'green-dark': '#1f9d55',
+ 'green': '#38c172',
+ 'green-light': '#51d88a',
+ 'green-lighter': '#a2f5bf',
+ 'green-lightest': '#e3fcec',
+
+ 'teal-darkest': '#0d3331',
+ 'teal-darker': '#174e4b',
+ 'teal-dark': '#38a89d',
+ 'teal': '#4dc0b5',
+ 'teal-light': '#64d5ca',
+ 'teal-lighter': '#a0f0ed',
+ 'teal-lightest': '#e8fffe',
+
+ 'blue-darkest': '#05233b',
+ 'blue-darker': '#103d60',
+ 'blue-dark': '#2779bd',
+ 'blue': '#3490dc',
+ 'blue-light': '#6cb2eb',
+ 'blue-lighter': '#bcdefa',
+ 'blue-lightest': '#eff8ff',
+
+ 'indigo-darkest': '#191e38',
+ 'indigo-darker': '#2f365f',
+ 'indigo-dark': '#5661b3',
+ 'indigo': '#6574cd',
+ 'indigo-light': '#7886d7',
+ 'indigo-lighter': '#b2b7ff',
+ 'indigo-lightest': '#e6e8ff',
+
+ 'purple-darkest': '#1f133f',
+ 'purple-darker': '#352465',
+ 'purple-dark': '#794acf',
+ 'purple': '#9561e2',
+ 'purple-light': '#a779e9',
+ 'purple-lighter': '#d6bbfc',
+ 'purple-lightest': '#f3ebff',
+
+ 'pink-darkest': '#45051e',
+ 'pink-darker': '#72173a',
+ 'pink-dark': '#eb5286',
+ 'pink': '#f66d9b',
+ 'pink-light': '#fa7ea8',
+ 'pink-lighter': '#ffbbca',
+ 'pink-lightest': '#ffebef'
+}
+
+module.exports = {
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Colors https://tailwindcss.com/docs/colors
+ |-----------------------------------------------------------------------------
+ |
+ | The color palette defined above is also assigned to the "colors" key of
+ | your Tailwind config. This makes it easy to access them in your CSS
+ | using Tailwind's config helper. For example:
+ |
+ | .error { color: config('colors.red') }
+ |
+ */
+
+ colors: colors,
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Screens https://tailwindcss.com/docs/responsive-design
+ |-----------------------------------------------------------------------------
+ |
+ | Screens in Tailwind are translated to CSS media queries. They define the
+ | responsive breakpoints for your project. By default Tailwind takes a
+ | "mobile first" approach, where each screen size represents a minimum
+ | viewport width. Feel free to have as few or as many screens as you
+ | want, naming them in whatever way you'd prefer for your project.
+ |
+ | Tailwind also allows for more complex screen definitions, which can be
+ | useful in certain situations. Be sure to see the full responsive
+ | documentation for a complete list of options.
+ |
+ | Class name: .{screen}:{utility}
+ |
+ */
+
+ screens: {
+ 'sm': '576px',
+ 'md': '768px',
+ 'lg': '992px',
+ 'xl': '1200px'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Fonts https://tailwindcss.com/docs/fonts
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your project's font stack, or font families.
+ | Keep in mind that Tailwind doesn't actually load any fonts for you.
+ | If you're using custom fonts you'll need to import them prior to
+ | defining them here.
+ |
+ | By default we provide a native font stack that works remarkably well on
+ | any device or OS you're using, since it just uses the default fonts
+ | provided by the platform.
+ |
+ | Class name: .font-{name}
+ |
+ */
+
+ fonts: {
+ 'sans': [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'Segoe UI',
+ 'Roboto',
+ 'Oxygen',
+ 'Ubuntu',
+ 'Cantarell',
+ 'Fira Sans',
+ 'Droid Sans',
+ 'Helvetica Neue'
+ ],
+ 'serif': [
+ 'Constantia',
+ 'Lucida Bright',
+ 'Lucidabright',
+ 'Lucida Serif',
+ 'Lucida',
+ 'DejaVu Serif',
+ 'Bitstream Vera Serif',
+ 'Liberation Serif',
+ 'Georgia',
+ 'serif'
+ ],
+ 'mono': [
+ 'Menlo',
+ 'Monaco',
+ 'Consolas',
+ 'Liberation Mono',
+ 'Courier New',
+ 'monospace'
+ ]
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Text sizes https://tailwindcss.com/docs/text-sizing
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your text sizes. Name these in whatever way
+ | makes the most sense to you. We use size names by default, but
+ | you're welcome to use a numeric scale or even something else
+ | entirely.
+ |
+ | By default Tailwind uses the "rem" unit type for most measurements.
+ | This allows you to set a root font size which all other sizes are
+ | then based on. That said, you are free to use whatever units you
+ | prefer, be it rems, ems, pixels or other.
+ |
+ | Class name: .text-{size}
+ |
+ */
+
+ textSizes: {
+ 'xs': '.75rem', // 12px
+ 'sm': '.875rem', // 14px
+ 'base': '1rem', // 16px
+ 'lg': '1.125rem', // 18px
+ 'xl': '1.25rem', // 20px
+ '2xl': '1.5rem', // 24px
+ '3xl': '1.875rem', // 30px
+ '4xl': '2.25rem', // 36px
+ '5xl': '3rem' // 48px
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Font weights https://tailwindcss.com/docs/font-weight
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your font weights. We've provided a list of
+ | common font weight names with their respective numeric scale values
+ | to get you started. It's unlikely that your project will require
+ | all of these, so we recommend removing those you don't need.
+ |
+ | Class name: .font-{weight}
+ |
+ */
+
+ fontWeights: {
+ 'hairline': 100,
+ 'thin': 200,
+ 'light': 300,
+ 'normal': 400,
+ 'medium': 500,
+ 'semibold': 600,
+ 'bold': 700,
+ 'extrabold': 800,
+ 'black': 900
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Leading (line height) https://tailwindcss.com/docs/line-height
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your line height values, or as we call
+ | them in Tailwind, leadings.
+ |
+ | Class name: .leading-{size}
+ |
+ */
+
+ leading: {
+ 'none': 1,
+ 'tight': 1.25,
+ 'normal': 1.5,
+ 'loose': 2
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Tracking (letter spacing) https://tailwindcss.com/docs/letter-spacing
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your letter spacing values, or as we call
+ | them in Tailwind, tracking.
+ |
+ | Class name: .tracking-{size}
+ |
+ */
+
+ tracking: {
+ 'tight': '-0.05em',
+ 'normal': '0',
+ 'wide': '0.05em'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Text colors https://tailwindcss.com/docs/text-color
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your text colors. By default these use the
+ | color palette we defined above, however you're welcome to set these
+ | independently if that makes sense for your project.
+ |
+ | Class name: .text-{color}
+ |
+ */
+
+ textColors: colors,
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Background colors https://tailwindcss.com/docs/background-color
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your background colors. By default these use
+ | the color palette we defined above, however you're welcome to set
+ | these independently if that makes sense for your project.
+ |
+ | Class name: .bg-{color}
+ |
+ */
+
+ backgroundColors: colors,
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Border widths https://tailwindcss.com/docs/border-width
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your border widths. Take note that border
+ | widths require a special "default" value set as well. This is the
+ | width that will be used when you do not specify a border width.
+ |
+ | Class name: .border{-side?}{-width?}
+ |
+ */
+
+ borderWidths: {
+ default: '1px',
+ '0': '0',
+ '2': '2px',
+ '4': '4px',
+ '8': '8px'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Border colors https://tailwindcss.com/docs/border-color
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your border colors. By default these use the
+ | color palette we defined above, however you're welcome to set these
+ | independently if that makes sense for your project.
+ |
+ | Take note that border colors require a special "default" value set
+ | as well. This is the color that will be used when you do not
+ | specify a border color.
+ |
+ | Class name: .border-{color}
+ |
+ */
+
+ borderColors: Object.assign({ default: colors['grey-light'] }, colors),
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Border radius https://tailwindcss.com/docs/border-radius
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your border radius values. If a `default` radius
+ | is provided, it will be made available as the non-suffixed `.rounded`
+ | utility.
+ |
+ | Class name: .rounded{-radius?}
+ |
+ */
+
+ borderRadius: {
+ default: '.25rem',
+ 'sm': '.125rem',
+ 'lg': '.5rem',
+ 'full': '9999px',
+ 'none': '0'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Width https://tailwindcss.com/docs/width
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your width utility sizes. These can be
+ | percentage based, pixels, rems, or any other units. By default
+ | we provide a sensible rem based numeric scale, a percentage
+ | based fraction scale, plus some other common use-cases. You
+ | can, of course, modify these values as needed.
+ |
+ |
+ | It's also worth mentioning that Tailwind automatically escapes
+ | invalid CSS class name characters, which allows you to have
+ | awesome classes like .w-2/3.
+ |
+ | Class name: .w-{size}
+ |
+ */
+
+ width: {
+ 'auto': 'auto',
+ 'px': '1px',
+ '1': '0.25rem',
+ '2': '0.5rem',
+ '3': '0.75rem',
+ '4': '1rem',
+ '6': '1.5rem',
+ '8': '2rem',
+ '10': '2.5rem',
+ '12': '3rem',
+ '16': '4rem',
+ '24': '6rem',
+ '32': '8rem',
+ '48': '12rem',
+ '64': '16rem',
+ '1/2': '50%',
+ '1/3': '33.33333%',
+ '2/3': '66.66667%',
+ '1/4': '25%',
+ '3/4': '75%',
+ '1/5': '20%',
+ '2/5': '40%',
+ '3/5': '60%',
+ '4/5': '80%',
+ '1/6': '16.66667%',
+ '5/6': '83.33333%',
+ 'full': '100%',
+ 'screen': '100vw'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Height https://tailwindcss.com/docs/height
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your height utility sizes. These can be
+ | percentage based, pixels, rems, or any other units. By default
+ | we provide a sensible rem based numeric scale plus some other
+ | common use-cases. You can, of course, modify these values as
+ | needed.
+ |
+ | Class name: .h-{size}
+ |
+ */
+
+ height: {
+ 'auto': 'auto',
+ 'px': '1px',
+ '1': '0.25rem',
+ '2': '0.5rem',
+ '3': '0.75rem',
+ '4': '1rem',
+ '6': '1.5rem',
+ '8': '2rem',
+ '10': '2.5rem',
+ '12': '3rem',
+ '16': '4rem',
+ '24': '6rem',
+ '32': '8rem',
+ '48': '12rem',
+ '64': '16rem',
+ 'full': '100%',
+ 'screen': '100vh'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Minimum width https://tailwindcss.com/docs/min-width
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your minimum width utility sizes. These can
+ | be percentage based, pixels, rems, or any other units. We provide a
+ | couple common use-cases by default. You can, of course, modify
+ | these values as needed.
+ |
+ | Class name: .min-w-{size}
+ |
+ */
+
+ minWidth: {
+ '0': '0',
+ 'full': '100%'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Minimum height https://tailwindcss.com/docs/min-height
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your minimum height utility sizes. These can
+ | be percentage based, pixels, rems, or any other units. We provide a
+ | few common use-cases by default. You can, of course, modify these
+ | values as needed.
+ |
+ | Class name: .min-h-{size}
+ |
+ */
+
+ minHeight: {
+ '0': '0',
+ 'full': '100%',
+ 'screen': '100vh'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Maximum width https://tailwindcss.com/docs/max-width
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your maximum width utility sizes. These can
+ | be percentage based, pixels, rems, or any other units. By default
+ | we provide a sensible rem based scale and a "full width" size,
+ | which is basically a reset utility. You can, of course,
+ | modify these values as needed.
+ |
+ | Class name: .max-w-{size}
+ |
+ */
+
+ maxWidth: {
+ 'xs': '20rem',
+ 'sm': '30rem',
+ 'md': '40rem',
+ 'lg': '50rem',
+ 'xl': '60rem',
+ '2xl': '70rem',
+ '3xl': '80rem',
+ '4xl': '90rem',
+ '5xl': '100rem',
+ 'full': '100%'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Maximum height https://tailwindcss.com/docs/max-height
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your maximum height utility sizes. These can
+ | be percentage based, pixels, rems, or any other units. We provide a
+ | couple common use-cases by default. You can, of course, modify
+ | these values as needed.
+ |
+ | Class name: .max-h-{size}
+ |
+ */
+
+ maxHeight: {
+ 'full': '100%',
+ 'screen': '100vh'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Padding https://tailwindcss.com/docs/padding
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your padding utility sizes. These can be
+ | percentage based, pixels, rems, or any other units. By default we
+ | provide a sensible rem based numeric scale plus a couple other
+ | common use-cases like "1px". You can, of course, modify these
+ | values as needed.
+ |
+ | Class name: .p{side?}-{size}
+ |
+ */
+
+ padding: {
+ 'px': '1px',
+ '0': '0',
+ '1': '0.25rem',
+ '2': '0.5rem',
+ '3': '0.75rem',
+ '4': '1rem',
+ '6': '1.5rem',
+ '8': '2rem'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Margin https://tailwindcss.com/docs/margin
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your margin utility sizes. These can be
+ | percentage based, pixels, rems, or any other units. By default we
+ | provide a sensible rem based numeric scale plus a couple other
+ | common use-cases like "1px". You can, of course, modify these
+ | values as needed.
+ |
+ | Class name: .m{side?}-{size}
+ |
+ */
+
+ margin: {
+ 'px': '1px',
+ '0': '0',
+ '1': '0.25rem',
+ '2': '0.5rem',
+ '3': '0.75rem',
+ '4': '1rem',
+ '6': '1.5rem',
+ '8': '2rem'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Negative margin https://tailwindcss.com/docs/negative-margin
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your negative margin utility sizes. These can
+ | be percentage based, pixels, rems, or any other units. By default we
+ | provide matching values to the padding scale since these utilities
+ | generally get used together. You can, of course, modify these
+ | values as needed.
+ |
+ | Class name: .-m{side?}-{size}
+ |
+ */
+
+ negativeMargin: {
+ 'px': '1px',
+ '0': '0',
+ '1': '0.25rem',
+ '2': '0.5rem',
+ '3': '0.75rem',
+ '4': '1rem',
+ '6': '1.5rem',
+ '8': '2rem'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Shadows https://tailwindcss.com/docs/shadows
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your shadow utilities. As you can see from
+ | the defaults we provide, it's possible to apply multiple shadows
+ | per utility using comma separation.
+ |
+ | If a `default` shadow is provided, it will be made available as the non-
+ | suffixed `.shadow` utility.
+ |
+ | Class name: .shadow-{size?}
+ |
+ */
+
+ shadows: {
+ default: '0 2px 4px 0 rgba(0,0,0,0.10)',
+ 'md': '0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)',
+ 'lg': '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)',
+ 'inner': 'inset 0 2px 4px 0 rgba(0,0,0,0.06)',
+ 'none': 'none'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Z-index https://tailwindcss.com/docs/z-index
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your z-index utility values. By default we
+ | provide a sensible numeric scale. You can, of course, modify these
+ | values as needed.
+ |
+ | Class name: .z-{index}
+ |
+ */
+
+ zIndex: {
+ '0': 0,
+ '10': 10,
+ '20': 20,
+ '30': 30,
+ '40': 40,
+ '50': 50,
+ 'auto': 'auto'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Opacity https://tailwindcss.com/docs/opacity
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you define your opacity utility values. By default we
+ | provide a sensible numeric scale. You can, of course, modify these
+ | values as needed.
+ |
+ | Class name: .opacity-{name}
+ |
+ */
+
+ opacity: {
+ '0': '0',
+ '25': '.25',
+ '50': '.5',
+ '75': '.75',
+ '100': '1'
+ },
+
+ /*
+ |-----------------------------------------------------------------------------
+ | Packages
+ |-----------------------------------------------------------------------------
+ |
+ | Here is where you can define the configuration for any Tailwind packages
+ | you're using. These can be utility packs, component bundles, or even
+ | complete themes. You'll want to reference each package's
+ | documentation for a list of settings available for it.
+ |
+ */
+
+ packages: {
+ }
+
+}
diff --git a/examples/typescript/components/Card.vue b/examples/typescript/components/Card.vue
index 13ace815b2..2886ffa17a 100644
--- a/examples/typescript/components/Card.vue
+++ b/examples/typescript/components/Card.vue
@@ -9,10 +9,10 @@
// **PLEASE NOTE** All "Nuxt Class Components" require at minimum a script tag that exports a default object
\ No newline at end of file
+
diff --git a/examples/typescript/store/index.ts b/examples/typescript/store/index.ts
index 292c381318..bfffcd5175 100644
--- a/examples/typescript/store/index.ts
+++ b/examples/typescript/store/index.ts
@@ -1,33 +1,33 @@
-import axios from "~plugins/axios";
-
-export const state = () => ({
- selected: 1,
- people: []
-});
-
-export const mutations = {
- select(state, id) {
- state.selected = id;
- },
- setPeople(state, people) {
- state.people = people;
- }
-};
-
-export const getters = {
- selectedPerson: state => {
- const p = state.people.find(person => person.id === state.selected);
- return p ? p : { first_name: "Please,", last_name: "select someone" };
- }
-};
-
-export const actions = {
- async nuxtServerInit({ commit }) {
- const response = await axios.get("/random-data.json");
- const people = response.data.slice(0, 10);
- commit("setPeople", people);
- },
- select({ commit }, id) {
- commit("select", id);
- }
-};
+import axios from "~/plugins/axios";
+
+export const state = () => ({
+ selected: 1,
+ people: []
+});
+
+export const mutations = {
+ select(state, id) {
+ state.selected = id;
+ },
+ setPeople(state, people) {
+ state.people = people;
+ }
+};
+
+export const getters = {
+ selectedPerson: state => {
+ const p = state.people.find(person => person.id === state.selected);
+ return p ? p : { first_name: "Please,", last_name: "select someone" };
+ }
+};
+
+export const actions = {
+ async nuxtServerInit({ commit }) {
+ const response = await axios.get("/random-data.json");
+ const people = response.data.slice(0, 10);
+ commit("setPeople", people);
+ },
+ select({ commit }, id) {
+ commit("select", id);
+ }
+};
diff --git a/examples/typescript/tsconfig.json b/examples/typescript/tsconfig.json
index e2812a86cd..557fd275fa 100644
--- a/examples/typescript/tsconfig.json
+++ b/examples/typescript/tsconfig.json
@@ -1,31 +1,29 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "es2015"
- ],
- "module": "es2015",
- "moduleResolution": "node",
- "experimentalDecorators": true,
- "declaration": true,
- "noImplicitAny": false,
- "noImplicitThis": false,
- "strictNullChecks": true,
- "removeComments": true,
- "suppressImplicitAnyIndexErrors": true,
- "allowSyntheticDefaultImports": true,
- "baseUrl": ".",
- "paths": {
- "~": ["./"],
- "~assets/*": ["./assets/*"],
- "~components/*": ["./components/*"],
- "~middleware/*": ["./middleware/*"],
- "~pages/*": ["./pages/*"],
- "~plugins/*": ["./plugins/*"],
- "~static/*": ["./static/*"],
- "~store": ["./.nuxt/store"],
- "~router": ["./.nuxt/router"]
- }
- }
-}
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "es2015"
+ ],
+ "module": "es2015",
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "noImplicitAny": false,
+ "noImplicitThis": false,
+ "strictNullChecks": true,
+ "removeComments": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "allowSyntheticDefaultImports": true,
+ "baseUrl": ".",
+ "allowJs": true,
+ "paths": {
+ "~/": ["./"],
+ "~/assets/*": ["./assets/*"],
+ "~/components/*": ["./components/*"],
+ "~/middleware/*": ["./middleware/*"],
+ "~/pages/*": ["./pages/*"],
+ "~/plugins/*": ["./plugins/*"],
+ "~/static/*": ["./static/*"]
+ }
+ }
+}
diff --git a/examples/uikit/README.md b/examples/uikit/README.md
new file mode 100644
index 0000000000..b24165bb26
--- /dev/null
+++ b/examples/uikit/README.md
@@ -0,0 +1,7 @@
+# UIKit with Nuxt.js
+
+UIkit: https://github.com/uikit/uikit
+
+Live demo: https://uikit.nuxtjs.org
+
+Live edit: https://glitch.com/edit/#!/nuxt-uitkit
diff --git a/examples/uikit/layouts/default.vue b/examples/uikit/layouts/default.vue
new file mode 100644
index 0000000000..a2a570ace7
--- /dev/null
+++ b/examples/uikit/layouts/default.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/examples/uikit/nuxt.config.js b/examples/uikit/nuxt.config.js
new file mode 100644
index 0000000000..6f916f758e
--- /dev/null
+++ b/examples/uikit/nuxt.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ css: ['uikit/dist/css/uikit.css'],
+ plugins: [
+ { src: '~/plugins/uikit.js', ssr: false }
+ ]
+}
diff --git a/examples/uikit/package.json b/examples/uikit/package.json
new file mode 100644
index 0000000000..bc539ba586
--- /dev/null
+++ b/examples/uikit/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "uikit-nuxt",
+ "dependencies": {
+ "nuxt": "latest",
+ "uikit": "^3.0.0-beta.30",
+ "jquery": "^3.2.1"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt start"
+ }
+}
diff --git a/examples/uikit/pages/about.vue b/examples/uikit/pages/about.vue
new file mode 100644
index 0000000000..67de726940
--- /dev/null
+++ b/examples/uikit/pages/about.vue
@@ -0,0 +1,19 @@
+
+
+
Hi from {{ name }}
+
+
+
+
Home page
+
+
+
+
diff --git a/examples/uikit/pages/index.vue b/examples/uikit/pages/index.vue
new file mode 100644
index 0000000000..ab5b67b26a
--- /dev/null
+++ b/examples/uikit/pages/index.vue
@@ -0,0 +1,12 @@
+
+
+
Nuxt.js + UIKIT
+
Hover
+
+
Click
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.
+
+
+
About page
+
+
diff --git a/examples/uikit/plugins/uikit.js b/examples/uikit/plugins/uikit.js
new file mode 100644
index 0000000000..81a92335a1
--- /dev/null
+++ b/examples/uikit/plugins/uikit.js
@@ -0,0 +1,5 @@
+import UIkit from 'uikit'
+import Icons from 'uikit/dist/js/uikit-icons'
+
+// loads the Icon plugin
+UIkit.use(Icons)
diff --git a/examples/vue-apollo/README.md b/examples/vue-apollo/README.md
index 8458ecb2d5..92ef54ec86 100644
--- a/examples/vue-apollo/README.md
+++ b/examples/vue-apollo/README.md
@@ -1,7 +1,7 @@
-# WIP
-
# Vue-Apollo with Nuxt.js
-https://nuxtjs.org/examples/vue-apollo
+Demo: https://nuxt-vue-apollo.now.sh/
+
+https://github.com/nuxt-community/apollo-module
https://github.com/Akryum/vue-apollo
diff --git a/examples/vue-apollo/apollo/network-interfaces/default.js b/examples/vue-apollo/apollo/network-interfaces/default.js
new file mode 100644
index 0000000000..7b6d3f6c14
--- /dev/null
+++ b/examples/vue-apollo/apollo/network-interfaces/default.js
@@ -0,0 +1,5 @@
+import { createNetworkInterface } from 'apollo-client'
+
+export default (ctx) => {
+ return createNetworkInterface({ uri: 'https://api.graph.cool/simple/v1/cj1dqiyvqqnmj0113yuqamkuu' })
+}
diff --git a/examples/vue-apollo/apollo/queries/allCars.gql b/examples/vue-apollo/apollo/queries/allCars.gql
new file mode 100644
index 0000000000..81c1448c14
--- /dev/null
+++ b/examples/vue-apollo/apollo/queries/allCars.gql
@@ -0,0 +1,8 @@
+{
+ allCars {
+ id
+ make
+ model
+ year
+ }
+}
diff --git a/examples/vue-apollo/apollo/queries/car.gql b/examples/vue-apollo/apollo/queries/car.gql
new file mode 100644
index 0000000000..c380d25884
--- /dev/null
+++ b/examples/vue-apollo/apollo/queries/car.gql
@@ -0,0 +1,8 @@
+query Car($id: ID!) {
+ Car(id: $id) {
+ make
+ model
+ photoURL
+ price
+ }
+}
diff --git a/examples/vue-apollo/middleware/apollo.js b/examples/vue-apollo/middleware/apollo.js
deleted file mode 100644
index 7b079c92f1..0000000000
--- a/examples/vue-apollo/middleware/apollo.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default async function ({ isServer, apolloProvider }) {
- if (isServer) {
- const ensureReady = apolloProvider.collect()
- console.log('Call ensureReady!', ensureReady())
- await ensureReady()
- }
-}
diff --git a/examples/vue-apollo/nuxt.config.js b/examples/vue-apollo/nuxt.config.js
index f73ea6eaf6..bdf96f2a93 100644
--- a/examples/vue-apollo/nuxt.config.js
+++ b/examples/vue-apollo/nuxt.config.js
@@ -1,12 +1,8 @@
module.exports = {
- build: {
- vendor: ['vue-apollo', 'apollo-client']
- },
- router: {
- middleware: 'apollo'
- },
- plugins: [
- // Will inject the plugin in the $root app and also in the context as `apolloProvider`
- { src: '~plugins/apollo.js', injectAs: 'apolloProvider' }
- ]
+ modules: ['@nuxtjs/apollo'],
+ apollo: {
+ networkInterfaces: {
+ default: '~/apollo/network-interfaces/default.js'
+ }
+ }
}
diff --git a/examples/vue-apollo/package.json b/examples/vue-apollo/package.json
index 2f823a1e8e..c9ffee9f88 100644
--- a/examples/vue-apollo/package.json
+++ b/examples/vue-apollo/package.json
@@ -1,13 +1,13 @@
{
- "name": "nuxt-i18n",
+ "name": "nuxt-vue-apollo",
"dependencies": {
- "apollo-client": "^1.0.3",
- "nuxt": "latest",
- "vue-apollo": "^2.1.0-beta.2"
+ "@nuxtjs/apollo": "^2.1.1",
+ "nuxt": "latest"
},
"scripts": {
- "dev": "node server.js",
+ "dev": "nuxt",
"build": "nuxt build",
- "start": "cross-env NODE_ENV=production node server.js"
+ "start": "nuxt start",
+ "generate": "nuxt generate"
}
}
diff --git a/examples/vue-apollo/pages/car/_id.vue b/examples/vue-apollo/pages/car/_id.vue
new file mode 100644
index 0000000000..b64cfe26f4
--- /dev/null
+++ b/examples/vue-apollo/pages/car/_id.vue
@@ -0,0 +1,45 @@
+
+
+
{{ Car.make }} {{ Car.model }}
+
{{ formatCurrency(Car.price) }}
+
+
Home page
+
+
+
+
+
+
diff --git a/examples/vue-apollo/pages/index.vue b/examples/vue-apollo/pages/index.vue
index bdb630f4ff..5aa6f9a5cd 100644
--- a/examples/vue-apollo/pages/index.vue
+++ b/examples/vue-apollo/pages/index.vue
@@ -12,21 +12,17 @@
diff --git a/examples/vue-apollo/plugins/apollo.js b/examples/vue-apollo/plugins/apollo.js
deleted file mode 100644
index cf888008c9..0000000000
--- a/examples/vue-apollo/plugins/apollo.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import 'babel-polyfill'
-import Vue from 'vue'
-import VueApollo from 'vue-apollo'
-import { ApolloClient, createNetworkInterface } from 'apollo-client'
-
-Vue.use(VueApollo)
-
-const API_ENDPOINT = 'https://api.graph.cool/simple/v1/cj1dqiyvqqnmj0113yuqamkuu'
-
-const apolloClient = new ApolloClient({
- networkInterface: createNetworkInterface({
- uri: API_ENDPOINT,
- transportBatching: true
- })
-})
-
-const apolloProvider = new VueApollo({
- defaultClient: apolloClient
-})
-
-export default apolloProvider
diff --git a/examples/vue-apollo/server.js b/examples/vue-apollo/server.js
deleted file mode 100644
index 32364be29a..0000000000
--- a/examples/vue-apollo/server.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const Nuxt = require('../../')
-const app = require('express')()
-const host = process.env.HOST || '127.0.0.1'
-const port = process.env.PORT || 3000
-
-global.fetch = require('node-fetch')
-
-// Import and Set Nuxt.js options
-let config = require('./nuxt.config.js')
-config.dev = !(process.env.NODE_ENV === 'production')
-
-// Init Nuxt.js
-const nuxt = new Nuxt(config)
-app.use(nuxt.render)
-
-// Build only in dev mode
-if (config.dev) {
- nuxt.build()
- .catch((error) => {
- console.error(error) // eslint-disable-line no-console
- process.exit(1)
- })
-}
-
-// Listen the server
-app.listen(port, host)
-console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console
diff --git a/examples/vue-chartjs/README.md b/examples/vue-chartjs/README.md
new file mode 100644
index 0000000000..ab534cf92a
--- /dev/null
+++ b/examples/vue-chartjs/README.md
@@ -0,0 +1,3 @@
+# Vue-ChartJS with Nuxt.js
+
+https://nuxtjs.org/examples
diff --git a/examples/vue-chartjs/components/bar-chart.js b/examples/vue-chartjs/components/bar-chart.js
new file mode 100644
index 0000000000..f6440d4b2f
--- /dev/null
+++ b/examples/vue-chartjs/components/bar-chart.js
@@ -0,0 +1,8 @@
+import { Bar } from 'vue-chartjs'
+
+export default Bar.extend({
+ props: ['data', 'options'],
+ mounted() {
+ this.renderChart(this.data, this.options)
+ }
+})
diff --git a/examples/vue-chartjs/components/doughnut-chart.js b/examples/vue-chartjs/components/doughnut-chart.js
new file mode 100644
index 0000000000..f972ab4dc9
--- /dev/null
+++ b/examples/vue-chartjs/components/doughnut-chart.js
@@ -0,0 +1,8 @@
+import { Doughnut } from 'vue-chartjs'
+
+export default Doughnut.extend({
+ props: ['data', 'options'],
+ mounted() {
+ this.renderChart(this.data, this.options)
+ }
+})
diff --git a/examples/vue-chartjs/layouts/default.vue b/examples/vue-chartjs/layouts/default.vue
new file mode 100644
index 0000000000..5c9abe4438
--- /dev/null
+++ b/examples/vue-chartjs/layouts/default.vue
@@ -0,0 +1,36 @@
+
+
+
+ Activity
+ Contributors
+
+
+
+
+
+
diff --git a/examples/vue-chartjs/nuxt.config.js b/examples/vue-chartjs/nuxt.config.js
new file mode 100644
index 0000000000..e6b67f1882
--- /dev/null
+++ b/examples/vue-chartjs/nuxt.config.js
@@ -0,0 +1,15 @@
+module.exports = {
+ head: {
+ title: 'Nuxt.js + Vue-ChartJS',
+ meta: [
+ { charset: 'utf-8' },
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' }
+ ]
+ },
+ build: {
+ vendor: ['axios', 'moment', 'chart.js', 'vue-chartjs']
+ },
+ env: {
+ githubToken: '42cdf9fd55abf41d24f34c0f8a4d9ada5f9e9b93'
+ }
+}
diff --git a/examples/vue-chartjs/package.json b/examples/vue-chartjs/package.json
new file mode 100644
index 0000000000..e3217175f5
--- /dev/null
+++ b/examples/vue-chartjs/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "vue-chartjs-nuxt",
+ "dependencies": {
+ "axios": "^0.16.2",
+ "chart.js": "^2.6.0",
+ "moment": "^2.18.1",
+ "nuxt": "latest",
+ "vue-chartjs": "^2.8.2"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt start"
+ }
+}
diff --git a/examples/vue-chartjs/pages/contributors.vue b/examples/vue-chartjs/pages/contributors.vue
new file mode 100644
index 0000000000..0d1a0649f8
--- /dev/null
+++ b/examples/vue-chartjs/pages/contributors.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/vue-chartjs/pages/index.vue b/examples/vue-chartjs/pages/index.vue
new file mode 100755
index 0000000000..d1d380470f
--- /dev/null
+++ b/examples/vue-chartjs/pages/index.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/vue-class-component/components/Base.vue b/examples/vue-class-component/components/Base.vue
new file mode 100644
index 0000000000..ed43328dda
--- /dev/null
+++ b/examples/vue-class-component/components/Base.vue
@@ -0,0 +1,40 @@
+
+
+
+
msg: {{msg}}
+
env: {{env}}
+
computed msg: {{computedMsg}}
+
Greet
+
About page
+
+
+
+
diff --git a/examples/vue-class-component/components/Child.vue b/examples/vue-class-component/components/Child.vue
new file mode 100644
index 0000000000..8063fc7bc4
--- /dev/null
+++ b/examples/vue-class-component/components/Child.vue
@@ -0,0 +1,23 @@
+
+
+
+
msg: {{msg}}
+
env: {{env}}
+
computed msg: {{computedMsg}}
+
Greet
+
About page
+
+
+
+
diff --git a/examples/vue-class-component/nuxt.config.js b/examples/vue-class-component/nuxt.config.js
index a61292b4ae..4d32a9ca0a 100644
--- a/examples/vue-class-component/nuxt.config.js
+++ b/examples/vue-class-component/nuxt.config.js
@@ -2,9 +2,6 @@ module.exports = {
build: {
babel: {
plugins: ['transform-decorators-legacy', 'transform-class-properties']
- },
- extend (config) {
- config.resolve.alias['nuxt-class-component'] = '~plugins/nuxt-class-component'
}
}
}
diff --git a/examples/vue-class-component/package.json b/examples/vue-class-component/package.json
index e672615065..797a48f36e 100644
--- a/examples/vue-class-component/package.json
+++ b/examples/vue-class-component/package.json
@@ -4,7 +4,7 @@
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"nuxt": "latest",
- "vue-class-component": "^5.0.1"
+ "nuxt-class-component": "^1.0.4"
},
"scripts": {
"dev": "nuxt",
diff --git a/examples/vue-class-component/pages/about.vue b/examples/vue-class-component/pages/about.vue
index 484c798b82..cbe04d7fda 100644
--- a/examples/vue-class-component/pages/about.vue
+++ b/examples/vue-class-component/pages/about.vue
@@ -1,3 +1,6 @@
- About
+
diff --git a/examples/vue-class-component/pages/index.vue b/examples/vue-class-component/pages/index.vue
index ce87828622..35e0116da2 100644
--- a/examples/vue-class-component/pages/index.vue
+++ b/examples/vue-class-component/pages/index.vue
@@ -1,41 +1,18 @@
-
-
-
msg: {{msg}}
-
env: {{env}}
-
computed msg: {{computedMsg}}
-
Greet
-
About page
-
+
diff --git a/examples/vue-class-component/plugins/nuxt-class-component.js b/examples/vue-class-component/plugins/nuxt-class-component.js
deleted file mode 100644
index 2f5188d00b..0000000000
--- a/examples/vue-class-component/plugins/nuxt-class-component.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Component from 'vue-class-component'
-
-Component.registerHooks([
- 'beforeRouteEnter',
- 'beforeRouteLeave',
- 'asyncData',
- 'fetch',
- 'middleware',
- 'layout',
- 'transition',
- 'scrollToTop'
-])
-
-export default Component
diff --git a/examples/vuex-persistedstate/README.md b/examples/vuex-persistedstate/README.md
new file mode 100644
index 0000000000..6827f6cf6a
--- /dev/null
+++ b/examples/vuex-persistedstate/README.md
@@ -0,0 +1,3 @@
+# Nuxt.js with Vuex persisted state (localStorage)
+
+See https://github.com/robinvdvleuten/vuex-persistedstate
diff --git a/examples/vuex-persistedstate/nuxt.config.js b/examples/vuex-persistedstate/nuxt.config.js
new file mode 100644
index 0000000000..274248d91b
--- /dev/null
+++ b/examples/vuex-persistedstate/nuxt.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ /*
+ ** We set `spa` mode to have only client-side rendering
+ */
+ mode: 'spa'
+}
diff --git a/examples/vuex-persistedstate/package.json b/examples/vuex-persistedstate/package.json
new file mode 100644
index 0000000000..5d9eef3ddf
--- /dev/null
+++ b/examples/vuex-persistedstate/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "nuxt-vuex-store",
+ "dependencies": {
+ "nuxt": "latest",
+ "vuex-persistedstate": "^2.0.0"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt start"
+ }
+}
diff --git a/examples/vuex-persistedstate/pages/index.vue b/examples/vuex-persistedstate/pages/index.vue
new file mode 100644
index 0000000000..8540d23c60
--- /dev/null
+++ b/examples/vuex-persistedstate/pages/index.vue
@@ -0,0 +1,22 @@
+
+
+
{{ counter }}
+
+ +
+ -
+
+
+
+
+
diff --git a/examples/vuex-persistedstate/store/index.js b/examples/vuex-persistedstate/store/index.js
new file mode 100644
index 0000000000..75a8434719
--- /dev/null
+++ b/examples/vuex-persistedstate/store/index.js
@@ -0,0 +1,14 @@
+import createPersistedState from 'vuex-persistedstate'
+
+export const state = () => ({
+ counter: 0
+})
+
+export const mutations = {
+ increment: (state) => state.counter++,
+ decrement: (state) => state.counter--
+}
+
+export const plugins = [
+ createPersistedState()
+]
diff --git a/examples/vuex-store-modules/pages/index.vue b/examples/vuex-store-modules/pages/index.vue
index 99a2cd37a5..33035823fc 100644
--- a/examples/vuex-store-modules/pages/index.vue
+++ b/examples/vuex-store-modules/pages/index.vue
@@ -23,14 +23,16 @@ import { mapState } from 'vuex'
export default {
// fetch(context) is called by the server-side
// and before instantiating the component
- fetch ({ store }) {
+ fetch({ store }) {
store.commit('increment')
},
computed: mapState([
'counter'
]),
methods: {
- increment () { this.$store.commit('increment') }
+ increment() {
+ this.$store.commit('increment')
+ }
}
}
diff --git a/examples/vuex-store-modules/pages/todos.vue b/examples/vuex-store-modules/pages/todos.vue
index a3aef45096..3c21b92a22 100644
--- a/examples/vuex-store-modules/pages/todos.vue
+++ b/examples/vuex-store-modules/pages/todos.vue
@@ -20,7 +20,7 @@ export default {
todos: 'todos/todos'
}),
methods: {
- addTodo (e) {
+ addTodo(e) {
var text = e.target.value
if (text.trim()) {
this.$store.commit('todos/add', { text })
diff --git a/examples/vuex-store-modules/store/articles.js b/examples/vuex-store-modules/store/articles.js
index e2fb76f674..a19b962c88 100644
--- a/examples/vuex-store-modules/store/articles.js
+++ b/examples/vuex-store-modules/store/articles.js
@@ -7,13 +7,13 @@ export const state = () => ({
})
export const mutations = {
- add (state, title) {
+ add(state, title) {
state.list.push(title)
}
}
export const getters = {
- get (state) {
+ get(state) {
return state.list
}
}
diff --git a/examples/vuex-store-modules/store/articles/comments.js b/examples/vuex-store-modules/store/articles/comments.js
index dd7f3c47c0..4092086eee 100644
--- a/examples/vuex-store-modules/store/articles/comments.js
+++ b/examples/vuex-store-modules/store/articles/comments.js
@@ -7,13 +7,13 @@ export const state = () => ({
})
export const mutations = {
- add (state, title) {
+ add(state, title) {
state.list.push(title)
}
}
export const getters = {
- get (state) {
+ get(state) {
return state.list
}
}
diff --git a/examples/vuex-store-modules/store/index.js b/examples/vuex-store-modules/store/index.js
index 63f1975bf2..b22221c9c7 100644
--- a/examples/vuex-store-modules/store/index.js
+++ b/examples/vuex-store-modules/store/index.js
@@ -3,7 +3,7 @@ export const state = () => ({
})
export const mutations = {
- increment (state) {
+ increment(state) {
state.counter++
}
}
diff --git a/examples/vuex-store-modules/store/todos.js b/examples/vuex-store-modules/store/todos.js
index 5063cbd1b8..f482ec1242 100644
--- a/examples/vuex-store-modules/store/todos.js
+++ b/examples/vuex-store-modules/store/todos.js
@@ -3,20 +3,20 @@ export const state = () => ({
})
export const mutations = {
- add (state, { text }) {
+ add(state, { text }) {
state.list.push({
text,
done: false
})
},
- toggle (state, todo) {
+ toggle(state, todo) {
todo.done = !todo.done
}
}
export const getters = {
- todos (state) {
+ todos(state) {
return state.list
}
}
diff --git a/examples/vuex-store/pages/index.vue b/examples/vuex-store/pages/index.vue
index 3ebbfdcac6..7406a84dd8 100644
--- a/examples/vuex-store/pages/index.vue
+++ b/examples/vuex-store/pages/index.vue
@@ -13,14 +13,16 @@ import { mapState } from 'vuex'
export default {
// fetch(context) is called by the server-side
// and nuxt before instantiating the component
- fetch ({ store }) {
+ fetch({ store }) {
store.commit('increment')
},
computed: mapState([
'counter'
]),
methods: {
- increment () { this.$store.commit('increment') }
+ increment() {
+ this.$store.commit('increment')
+ }
}
}
diff --git a/examples/vuex-store/store/mutations.js b/examples/vuex-store/store/mutations.js
index e85cce517e..81783007a1 100644
--- a/examples/vuex-store/store/mutations.js
+++ b/examples/vuex-store/store/mutations.js
@@ -1,7 +1,7 @@
const mutations = {
- increment (state) {
- state.counter++
- }
+ increment(state) {
+ state.counter++
+ }
}
-export default mutations
\ No newline at end of file
+export default mutations
diff --git a/examples/with-apollo/README.md b/examples/with-apollo/README.md
deleted file mode 100644
index d4600552fb..0000000000
--- a/examples/with-apollo/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# nuxt-with-apollo
-
-> Nuxt.js with Apollo (GraphQL client)
-
-[DEMO](https://nuxt-apollo.now.sh/)
-
-## About
-
-This project uses [Apollo](http://www.apollodata.com/) as a GraphQL client and [Graphcool](https://www.graph.cool/) as a hosted GraphQL backend.
-
-## Getting Started
-
-Download this example [or clone the repo](https://github.com/nuxt/nuxt.js):
-
-```bash
-curl https://codeload.github.com/nuxt/nuxt.js/tar.gz/master | tar -xz --strip=2 nuxt.js-master/examples/with-apollo
-cd with-apollo
-```
-
-Install and run:
-
-```bash
-npm install
-npm run dev
-
-# or with Yarn
-yarn
-yarn dev
-```
diff --git a/examples/with-apollo/layouts/default.vue b/examples/with-apollo/layouts/default.vue
deleted file mode 100644
index 62147ffc78..0000000000
--- a/examples/with-apollo/layouts/default.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
Nuxt.js + Apollo
-
-
-
-
-
diff --git a/examples/with-apollo/layouts/error.vue b/examples/with-apollo/layouts/error.vue
deleted file mode 100644
index 192163dd31..0000000000
--- a/examples/with-apollo/layouts/error.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
Page not found
- An error occured
- ← Home page
-
-
-
-
diff --git a/examples/with-apollo/package.json b/examples/with-apollo/package.json
deleted file mode 100644
index c3bb76f0f4..0000000000
--- a/examples/with-apollo/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "nuxt-apollo",
- "version": "1.0.0",
- "description": "Nuxt.js with Apollo",
- "author": "Charlie Hield",
- "license": "MIT",
- "scripts": {
- "dev": "nuxt",
- "build": "nuxt build",
- "start": "nuxt start"
- },
- "keywords": [
- "nuxt",
- "vue",
- "apollo",
- "graphql"
- ],
- "dependencies": {
- "apollo-client": "^1.0.2",
- "graphql-tag": "^2.0.0",
- "isomorphic-fetch": "^2.2.1",
- "nuxt": "^0.10.5"
- }
-}
diff --git a/examples/with-apollo/pages/car/_id.vue b/examples/with-apollo/pages/car/_id.vue
deleted file mode 100644
index 955b9475ec..0000000000
--- a/examples/with-apollo/pages/car/_id.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
{{ car.make }} {{ car.model }}
-
{{ formatCurrency(car.price) }}
-
-
Home page
-
-
-
-
-
-
diff --git a/examples/with-apollo/pages/index.vue b/examples/with-apollo/pages/index.vue
deleted file mode 100644
index b8c6a7d7df..0000000000
--- a/examples/with-apollo/pages/index.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
Cars
-
-
-
- {{ car.year }} {{ car.make }} {{ car.model }}
-
-
-
-
-
-
-
-
-
diff --git a/examples/with-apollo/plugins/apollo.js b/examples/with-apollo/plugins/apollo.js
deleted file mode 100644
index f328ff1fc7..0000000000
--- a/examples/with-apollo/plugins/apollo.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import Vue from 'vue'
-import { ApolloClient, createNetworkInterface } from 'apollo-client'
-import 'isomorphic-fetch'
-
-// Created with Graphcool - https://www.graph.cool/
-const API_ENDPOINT = 'https://api.graph.cool/simple/v1/cj1dqiyvqqnmj0113yuqamkuu'
-
-const apolloClient = new ApolloClient({
- networkInterface: createNetworkInterface({
- uri: API_ENDPOINT,
- transportBatching: true
- })
-})
-
-export default apolloClient
diff --git a/examples/with-ava/package.json b/examples/with-ava/package.json
index f8925f9997..02065f4c57 100755
--- a/examples/with-ava/package.json
+++ b/examples/with-ava/package.json
@@ -11,6 +11,6 @@
"jsdom": "^11.0.0"
},
"dependencies": {
- "nuxt": "^1.0.0-alpha2"
+ "nuxt": "latest"
}
}
diff --git a/examples/with-ava/pages/index.vue b/examples/with-ava/pages/index.vue
index a3d80ae170..b411fa4762 100755
--- a/examples/with-ava/pages/index.vue
+++ b/examples/with-ava/pages/index.vue
@@ -4,7 +4,7 @@
+
+
diff --git a/examples/with-cookies/plugins/cookies.js b/examples/with-cookies/plugins/cookies.js
new file mode 100644
index 0000000000..145baaec12
--- /dev/null
+++ b/examples/with-cookies/plugins/cookies.js
@@ -0,0 +1,34 @@
+import Vue from 'vue'
+import Cookie from 'cookie'
+import JSCookie from 'js-cookie'
+
+// Called only on client-side
+export const getCookies = (str) => {
+ return Cookie.parse(str || '')
+}
+
+/*
+** Executed by ~/.nuxt/index.js with context given
+** This method can be asynchronous
+*/
+export default ({ req }, inject) => {
+ // Inject `cookies` key
+ // -> app.$cookies
+ // -> this.$cookies in vue components
+ // -> this.$cookies in store actions/mutations
+ inject('cookies', new Vue({
+ data: () => ({
+ cookies: getCookies(process.server ? req.headers.cookie : document.cookie)
+ }),
+ methods: {
+ set(...args) {
+ JSCookie.set(...args)
+ this.cookies = getCookies(document.cookie)
+ },
+ remove(...args) {
+ JSCookie.remove(...args)
+ this.cookies = getCookies(document.cookie)
+ }
+ }
+ }))
+}
diff --git a/examples/with-element-ui/layouts/default.vue b/examples/with-element-ui/layouts/default.vue
new file mode 100644
index 0000000000..184d6f6fd3
--- /dev/null
+++ b/examples/with-element-ui/layouts/default.vue
@@ -0,0 +1,10 @@
+
+
+
+ Element Example
+
+
+
+
+
+
diff --git a/examples/with-element-ui/nuxt.config.js b/examples/with-element-ui/nuxt.config.js
new file mode 100644
index 0000000000..643f79ee78
--- /dev/null
+++ b/examples/with-element-ui/nuxt.config.js
@@ -0,0 +1,15 @@
+module.exports = {
+ /*
+ ** Global CSS
+ */
+ css: [
+ 'element-ui/lib/theme-default/index.css'
+ ],
+
+ /*
+ ** Add element-ui in our app, see plugins/element-ui.js file
+ */
+ plugins: [
+ '@/plugins/element-ui'
+ ]
+}
diff --git a/examples/with-element-ui/package.json b/examples/with-element-ui/package.json
new file mode 100644
index 0000000000..d5696a30ea
--- /dev/null
+++ b/examples/with-element-ui/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "with-element-ui",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "element-ui": "^1.4.9",
+ "nuxt": "latest"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt start"
+ },
+ "devDependencies": {
+ "node-sass": "^4.6.0",
+ "sass-loader": "^6.0.6"
+ }
+}
diff --git a/examples/with-element-ui/pages/index.vue b/examples/with-element-ui/pages/index.vue
new file mode 100644
index 0000000000..69c4a92771
--- /dev/null
+++ b/examples/with-element-ui/pages/index.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Medium
+ High
+
+
+
+
+
+ Create
+ Reset
+
+
+
+
+
+
+
+
diff --git a/examples/with-element-ui/plugins/element-ui.js b/examples/with-element-ui/plugins/element-ui.js
new file mode 100644
index 0000000000..cb618add96
--- /dev/null
+++ b/examples/with-element-ui/plugins/element-ui.js
@@ -0,0 +1,7 @@
+import Vue from 'vue'
+import Element from 'element-ui/lib/element-ui.common'
+import locale from 'element-ui/lib/locale/lang/en'
+
+export default () => {
+ Vue.use(Element, { locale })
+}
diff --git a/examples/with-feathers/.eslintrc.js b/examples/with-feathers/.eslintrc.js
new file mode 100644
index 0000000000..0bdc518e5c
--- /dev/null
+++ b/examples/with-feathers/.eslintrc.js
@@ -0,0 +1,36 @@
+module.exports = {
+ root: true,
+ parser: 'babel-eslint',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ env: {
+ browser: true,
+ node: true,
+ mocha: true
+ },
+ extends: 'standard',
+ // required to lint *.vue files
+ plugins: [
+ 'html'
+ ],
+ // add your custom rules here
+ rules: {
+ // allow paren-less arrow functions
+ 'arrow-parens': 0,
+ // allow async-await
+ 'generator-star-spacing': 0,
+ // allow debugger during development
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+ // do not allow console.logs etc...
+ 'no-console': 2,
+ 'space-before-function-paren': [
+ 2,
+ {
+ anonymous: 'always',
+ named: 'never'
+ }
+ ],
+ },
+ globals: {}
+}
diff --git a/examples/with-feathers/nuxt.config.js b/examples/with-feathers/nuxt.config.js
index f982017753..1fec96a57b 100644
--- a/examples/with-feathers/nuxt.config.js
+++ b/examples/with-feathers/nuxt.config.js
@@ -2,4 +2,4 @@ module.exports = {
loading: {
color: 'purple'
}
-};
+}
diff --git a/examples/with-feathers/package.json b/examples/with-feathers/package.json
index 3468c1aead..fc99464a7f 100644
--- a/examples/with-feathers/package.json
+++ b/examples/with-feathers/package.json
@@ -37,6 +37,7 @@
"winston": "^2.3.0"
},
"devDependencies": {
+ "eslint-plugin-mocha": "^4.11.0",
"jshint": "^2.9.4",
"mocha": "^3.2.0",
"nodemon": "^1.11.0",
diff --git a/examples/with-feathers/pages/about.vue b/examples/with-feathers/pages/about.vue
index 2dcf808b30..a16b409edc 100644
--- a/examples/with-feathers/pages/about.vue
+++ b/examples/with-feathers/pages/about.vue
@@ -7,9 +7,9 @@
diff --git a/examples/with-firebase/pages/users/_key.vue b/examples/with-firebase/pages/users/_key.vue
index 94a2735870..410842a241 100644
--- a/examples/with-firebase/pages/users/_key.vue
+++ b/examples/with-firebase/pages/users/_key.vue
@@ -11,15 +11,13 @@
diff --git a/examples/with-museui/nuxt.config.js b/examples/with-museui/nuxt.config.js
new file mode 100644
index 0000000000..55a86f08c1
--- /dev/null
+++ b/examples/with-museui/nuxt.config.js
@@ -0,0 +1,26 @@
+module.exports = {
+ head: {
+ meta: [
+ {
+ name: 'viewport',
+ content: 'width=device-width, initial-scale=1'
+ }
+ ],
+ link: [
+ {
+ rel: 'stylesheet',
+ href:
+ 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic'
+ },
+ {
+ rel: 'stylesheet',
+ href: 'https://fonts.googleapis.com/icon?family=Material+Icons'
+ },
+ {
+ rel: 'stylesheet',
+ href: 'https://unpkg.com/muse-ui@2.1.0/dist/muse-ui.css'
+ }
+ ]
+ },
+ plugins: ['~/plugins/museui']
+}
diff --git a/examples/with-museui/package.json b/examples/with-museui/package.json
new file mode 100644
index 0000000000..6f88ac897c
--- /dev/null
+++ b/examples/with-museui/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "with-museui",
+ "dependencies": {
+ "nuxt": "latest",
+ "muse-ui": "latest"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt start"
+ }
+}
diff --git a/examples/with-museui/pages/index.vue b/examples/with-museui/pages/index.vue
new file mode 100644
index 0000000000..0d1973e82f
--- /dev/null
+++ b/examples/with-museui/pages/index.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/with-museui/plugins/museui.js b/examples/with-museui/plugins/museui.js
new file mode 100644
index 0000000000..623286c123
--- /dev/null
+++ b/examples/with-museui/plugins/museui.js
@@ -0,0 +1,4 @@
+import Vue from 'vue'
+import MuseUI from 'muse-ui'
+
+Vue.use(MuseUI)
diff --git a/examples/with-sockets/io/index.js b/examples/with-sockets/io/index.js
new file mode 100644
index 0000000000..53560a8eb7
--- /dev/null
+++ b/examples/with-sockets/io/index.js
@@ -0,0 +1,24 @@
+module.exports = function () {
+ const server = require('http').createServer(this.nuxt.renderer.app)
+ const io = require('socket.io')(server)
+
+ // overwrite nuxt.listen()
+ this.nuxt.listen = (port, host) => new Promise((resolve) => server.listen(port || 3000, host || 'localhost', resolve))
+ // close this server on 'close' event
+ this.nuxt.plugin('close', () => new Promise((resolve) => server.close(resolve)))
+
+ // Add `socket.io-client` in vendor
+ this.addVendor('socket.io-client')
+
+ // Add socket.io events
+ let messages = []
+ io.on('connection', (socket) => {
+ socket.on('last-messages', function (fn) {
+ fn(messages.slice(-50))
+ })
+ socket.on('send-message', function (message) {
+ messages.push(message)
+ socket.broadcast.emit('new-message', message)
+ })
+ })
+}
diff --git a/examples/with-sockets/nuxt.config.js b/examples/with-sockets/nuxt.config.js
index 7ca9c60b1f..34e2ec30a1 100644
--- a/examples/with-sockets/nuxt.config.js
+++ b/examples/with-sockets/nuxt.config.js
@@ -1,14 +1,12 @@
module.exports = {
- build: {
- vendor: ['socket.io-client']
- },
head: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
},
+ modules: ['~/io'],
env: {
- HOST_URL: process.env.HOST_URL || 'http://localhost:3000'
+ WS_URL: process.env.WS_URL || 'http://localhost:3000'
}
}
diff --git a/examples/with-sockets/package.json b/examples/with-sockets/package.json
index 4f4c523957..b29200ff34 100644
--- a/examples/with-sockets/package.json
+++ b/examples/with-sockets/package.json
@@ -7,7 +7,7 @@
},
"dependencies": {
"express": "^4.14.0",
- "nuxt": "^0.9.5",
+ "nuxt": "latest",
"socket.io": "^1.7.2",
"socket.io-client": "^1.7.2"
},
diff --git a/examples/with-sockets/pages/index.vue b/examples/with-sockets/pages/index.vue
index 4e4f66c714..7184116d06 100644
--- a/examples/with-sockets/pages/index.vue
+++ b/examples/with-sockets/pages/index.vue
@@ -14,10 +14,10 @@
+
+
diff --git a/examples/with-tape/test/index.test.js b/examples/with-tape/test/index.test.js
new file mode 100755
index 0000000000..7d46c3d11e
--- /dev/null
+++ b/examples/with-tape/test/index.test.js
@@ -0,0 +1,43 @@
+import test from 'tape'
+import { shallow } from 'vue-test-utils'
+import Index from '../pages/index.vue'
+
+test('renders Index.vue correctly', t => {
+ t.plan(4)
+
+ const wrapper = shallow(Index, {
+ data: {
+ name: 'nuxt'
+ }
+ })
+
+ const button = wrapper.find('button')
+
+ t.equal(
+ wrapper.find('h1').text(),
+ 'Hello nuxt!',
+ 'renders "Hello nuxt!" text'
+ )
+
+ t.equal(
+ wrapper.find('h1').hasClass('red'),
+ true,
+ 'h1 has a red class [default]'
+ )
+
+ button.trigger('click')
+
+ t.equal(
+ wrapper.find('h1').hasClass('blue'),
+ true,
+ 'h1 class changes to blue [after 1st click]'
+ )
+
+ button.trigger('click')
+
+ t.equal(
+ wrapper.find('h1').hasClass('green'),
+ true,
+ 'h1 class changes to green [after 2nd click]'
+ )
+})
diff --git a/examples/with-tape/test/setup.js b/examples/with-tape/test/setup.js
new file mode 100644
index 0000000000..69cf3aa353
--- /dev/null
+++ b/examples/with-tape/test/setup.js
@@ -0,0 +1,14 @@
+const hooks = require('require-extension-hooks')
+
+// Setup browser environment
+require('browser-env')()
+
+// Setup vue files to be processed by `require-extension-hooks-vue`
+hooks('vue')
+ .plugin('vue')
+ .push()
+
+// Setup vue and js files to be processed by `require-extension-hooks-babel`
+hooks(['vue', 'js'])
+ .plugin('babel')
+ .push()
diff --git a/examples/with-vuetify/css/vendor/vuetify.styl b/examples/with-vuetify/assets/app.styl
similarity index 86%
rename from examples/with-vuetify/css/vendor/vuetify.styl
rename to examples/with-vuetify/assets/app.styl
index 6865590deb..66d746eb17 100644
--- a/examples/with-vuetify/css/vendor/vuetify.styl
+++ b/examples/with-vuetify/assets/app.styl
@@ -11,4 +11,4 @@ $theme := {
}
// Import Vuetify styling
-@require '~vuetify/src/stylus/main.styl'
+@require '~vuetify/src/stylus/main.styl'
\ No newline at end of file
diff --git a/examples/with-vuetify/css/app.styl b/examples/with-vuetify/css/app.styl
deleted file mode 100644
index cd1c8f9dc8..0000000000
--- a/examples/with-vuetify/css/app.styl
+++ /dev/null
@@ -1,2 +0,0 @@
-@require './vendor/material-icons.styl'
-@require './vendor/vuetify.styl'
diff --git a/examples/with-vuetify/css/vendor/material-icons.styl b/examples/with-vuetify/css/vendor/material-icons.styl
deleted file mode 100644
index a063664368..0000000000
--- a/examples/with-vuetify/css/vendor/material-icons.styl
+++ /dev/null
@@ -1,21 +0,0 @@
-@font-face {
- font-family: 'Material Icons';
- font-style: normal;
- font-weight: 400;
- src: local('Material Icons'), local('MaterialIcons-Regular'), url(https://fonts.gstatic.com/s/materialicons/v22/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2');
-}
-.material-icons {
- font-family: 'Material Icons';
- font-weight: normal;
- font-style: normal;
- font-size: 24px;
- line-height: 1;
- letter-spacing: normal;
- text-transform: none;
- display: inline-block;
- white-space: nowrap;
- word-wrap: normal;
- direction: ltr;
- -webkit-font-feature-settings: 'liga';
- -webkit-font-smoothing: antialiased;
-}
diff --git a/examples/with-vuetify/layouts/default.vue b/examples/with-vuetify/layouts/default.vue
new file mode 100644
index 0000000000..7c8e435f8a
--- /dev/null
+++ b/examples/with-vuetify/layouts/default.vue
@@ -0,0 +1,45 @@
+
+
+
+
+ Toolbar
+
+
+
+
+
+ Home
+
+
+
+
+ About
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/with-vuetify/nuxt.config.js b/examples/with-vuetify/nuxt.config.js
index a01ebf437d..df74955493 100644
--- a/examples/with-vuetify/nuxt.config.js
+++ b/examples/with-vuetify/nuxt.config.js
@@ -1,17 +1,26 @@
-
-const { join } = require('path')
-
module.exports = {
- build: {
- vendor: ['vuetify']
- },
- plugins: ['~plugins/vuetify.js'],
- css: [
- { src: join(__dirname, 'css/app.styl'), lang: 'styl' }
- ],
+ /*
+ ** Head elements
+ ** Add Roboto font and Material Icons
+ */
head: {
link: [
- { rel: 'preload', as: 'style', href: 'https://fonts.googleapis.com/css?family=Roboto' }
+ { rel: 'stylesheet', type: 'text/css', href: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' }
]
- }
+ },
+ /*
+ ** Add Vuetify into vendor.bundle.js
+ */
+ build: {
+ vendor: ['vuetify'],
+ extractCSS: true
+ },
+ /*
+ ** Load Vuetify into the app
+ */
+ plugins: ['~/plugins/vuetify'],
+ /*
+ ** Load Vuetify CSS globally
+ */
+ css: ['~/assets/app.styl']
}
diff --git a/examples/with-vuetify/package.json b/examples/with-vuetify/package.json
index baa308ca69..646947ae35 100644
--- a/examples/with-vuetify/package.json
+++ b/examples/with-vuetify/package.json
@@ -1,16 +1,14 @@
{
"name": "with-vuetify",
"dependencies": {
- "nuxt": "^0.10.7",
- "vuetify": "^0.11.1"
+ "nuxt": "latest",
+ "vuetify": "latest"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
- "generate": "nuxt generate",
- "predeploy": "yarn run generate",
- "deploy": "surge --domain nuxt-with-vuetify-example.surge.sh dist"
+ "generate": "nuxt generate"
},
"devDependencies": {
"stylus": "^0.54.5",
diff --git a/examples/with-vuetify/pages/about.vue b/examples/with-vuetify/pages/about.vue
new file mode 100644
index 0000000000..4a926dad1b
--- /dev/null
+++ b/examples/with-vuetify/pages/about.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/with-vuetify/pages/index.vue b/examples/with-vuetify/pages/index.vue
index 80916b3b54..52573bcb7d 100644
--- a/examples/with-vuetify/pages/index.vue
+++ b/examples/with-vuetify/pages/index.vue
@@ -1,45 +1,8 @@
-
-
-
- Toolbar
-
-
-
-
-
-
- Item {{ i }}
-
-
-
-
-
-
-
-
Main content
- Primary button
- Secondary button
- Success button
-
-
-
-
-
-
-
-
-
-
+
+
Main content
+ Primary button
+ Secondary button
+ Success button
+
+
\ No newline at end of file
diff --git a/index.d.ts b/index.d.ts
deleted file mode 100644
index 29b48d25f3..0000000000
--- a/index.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-//These declarations allow TypeScript to import non-js/ts files without the file extensions (such as .vue files)
-
-declare module "~components/*" {}
-declare module "~layouts/*" {}
-declare module "~pages/*" {}
-declare module "~assets/*" {}
-declare module "~static/*" {}
diff --git a/index.js b/index.js
index 8e7487a896..24e035805e 100644
--- a/index.js
+++ b/index.js
@@ -1,11 +1,16 @@
/*!
* Nuxt.js
* (c) 2016-2017 Chopin Brothers
+ * Core maintainer: Pooya (@pi0)
* Released under the MIT License.
*/
+// Node Source Map Support
+// https://github.com/evanw/node-source-map-support
+require('source-map-support').install()
+
+// Fix babel flag
+/* istanbul ignore else */
process.noDeprecation = true
-var Nuxt = require('./dist/nuxt.js')
-
-module.exports = Nuxt.default ? Nuxt.default : Nuxt
+module.exports = require('./dist/nuxt')
diff --git a/lib/app/App.js b/lib/app/App.js
new file mode 100644
index 0000000000..0d0e719837
--- /dev/null
+++ b/lib/app/App.js
@@ -0,0 +1,109 @@
+import Vue from 'vue'
+<% if (loading) { %>import NuxtLoading from '<%= (typeof loading === "string" ? loading : "./components/nuxt-loading.vue") %>'<% } %>
+<% css.forEach(function (c) { %>
+import '<%= relativeToBuild(resolvePath(c.src || c)) %>'
+<% }) %>
+
+let layouts = {
+<%
+var layoutsKeys = Object.keys(layouts);
+layoutsKeys.forEach(function (key, i) { %>
+ "_<%= key %>": () => import('<%= layouts[key] %>' /* webpackChunkName: "<%= wChunk('layouts/'+key) %>" */).then(m => m.default || m)<%= (i + 1) < layoutsKeys.length ? ',' : '' %>
+<% }) %>
+}
+
+let resolvedLayouts = {}
+
+export default {
+ head: <%= serialize(head).replace('head(', 'function(').replace('titleTemplate(', 'function(') %>,
+ render(h, props) {
+ <% if (loading) { %>const loadingEl = h('nuxt-loading', { ref: 'loading' })<% } %>
+ const layoutEl = h(this.layout || 'nuxt')
+ const templateEl = h('div', {
+ domProps: {
+ id: '__layout'
+ },
+ key: this.layoutName
+ }, [ layoutEl ])
+
+ const transitionEl = h('transition', {
+ props: {
+ name: '<%= layoutTransition.name %>',
+ mode: '<%= layoutTransition.mode %>'
+ }
+ }, [ templateEl ])
+
+ return h('div',{
+ domProps: {
+ id: '__nuxt'
+ }
+ }, [
+ <% if (loading) { %>loadingEl,<% } %>
+ transitionEl
+ ])
+ },
+ data: () => ({
+ layout: null,
+ layoutName: ''
+ }),
+ beforeCreate () {
+ Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt)
+ },
+ created () {
+ // Add this.$nuxt in child instances
+ Vue.prototype.$nuxt = this
+ // add to window so we can listen when ready
+ if (typeof window !== 'undefined') {
+ window.$nuxt = this
+ }
+ // Add $nuxt.error()
+ this.error = this.nuxt.error
+ },
+ <% if (loading) { %>
+ mounted () {
+ this.$loading = this.$refs.loading
+ },
+ watch: {
+ 'nuxt.err': 'errorChanged'
+ },
+ <% } %>
+ methods: {
+ <% if (loading) { %>
+ errorChanged () {
+ if (this.nuxt.err && this.$loading) {
+ if (this.$loading.fail) this.$loading.fail()
+ if (this.$loading.finish) this.$loading.finish()
+ }
+ },
+ <% } %>
+ setLayout (layout) {
+ if (!layout || !resolvedLayouts['_' + layout]) layout = 'default'
+ this.layoutName = layout
+ let _layout = '_' + layout
+ this.layout = resolvedLayouts[_layout]
+ return this.layout
+ },
+ loadLayout (layout) {
+ if (!layout || !(layouts['_' + layout] || resolvedLayouts['_' + layout])) layout = 'default'
+ let _layout = '_' + layout
+ if (resolvedLayouts[_layout]) {
+ return Promise.resolve(resolvedLayouts[_layout])
+ }
+ return layouts[_layout]()
+ .then((Component) => {
+ resolvedLayouts[_layout] = Component
+ delete layouts[_layout]
+ return resolvedLayouts[_layout]
+ })
+ .catch((e) => {
+ if (this.$nuxt) {
+ return this.$nuxt.error({ statusCode: 500, message: e.message })
+ }
+ })
+ }
+ },
+ components: {
+ <%= (loading ? 'NuxtLoading' : '') %>
+ }
+}
+
diff --git a/lib/app/App.vue b/lib/app/App.vue
deleted file mode 100644
index fa8ef6d403..0000000000
--- a/lib/app/App.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
- <% if (loading) { %> <% } %>
-
-
-
-
-
-
diff --git a/lib/app/client.js b/lib/app/client.js
index ccb2381d02..216b8ded79 100644
--- a/lib/app/client.js
+++ b/lib/app/client.js
@@ -1,185 +1,354 @@
-'use strict'
-
import Vue from 'vue'
import middleware from './middleware'
import { createApp, NuxtError } from './index'
-import { applyAsyncData, sanitizeComponent, getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, middlewareSeries, promisify, getLocation, compile } from './utils'
+import {
+ applyAsyncData,
+ sanitizeComponent,
+ resolveRouteComponents,
+ getMatchedComponents,
+ getMatchedComponentsInstances,
+ flatMapComponents,
+ setContext,
+ middlewareSeries,
+ promisify,
+ getLocation,
+ compile,
+ getQueryDiff
+} from './utils'
+
const noopData = () => { return {} }
const noopFetch = () => {}
-let _lastPaths = []
-let _lastComponentsFiles = []
+// Global shared references
+let _lastPaths = []
let app
let router
-<%= (store ? 'let store' : '') %>
+<% if (store) { %>let store<% } %>
+
+// Try to rehydrate SSR data from window
+const NUXT = window.__NUXT__ || {}
+
+<% if (debug || mode === 'spa') { %>
+// Setup global Vue error handler
+const defaultErrorHandler = Vue.config.errorHandler
+Vue.config.errorHandler = function (err, vm, info) {
+ const nuxtError = {
+ statusCode: err.statusCode || err.name || 'Whoops!',
+ message: err.message || err.toString()
+ }
+
+ // Show Nuxt Error Page
+ if(vm && vm.$root && vm.$root.$nuxt && vm.$root.$nuxt.error && info !== 'render function') {
+ vm.$root.$nuxt.error(nuxtError)
+ }
+
+ // Call other handler if exist
+ if (typeof defaultErrorHandler === 'function') {
+ return defaultErrorHandler(...arguments)
+ }
+
+ // Log to console
+ if (process.env.NODE_ENV !== 'production') {
+ console.error(err)
+ } else {
+ console.error(err.message || nuxtError.message)
+ }
+}
+<% } %>
+
+// Create and mount App
+createApp()
+.then(mountApp)
+.catch(err => {
+ console.error('[nuxt] Error while initializing app', err)
+})
+
+function componentOption(component, key, ...args) {
+ if (!component || !component.options || !component.options[key]) {
+ return {}
+ }
+ const option = component.options[key]
+ if (typeof option === 'function') {
+ return option(...args)
+ }
+ return option
+}
function mapTransitions(Components, to, from) {
- return Components.map((Component) => {
- let transition = Component.options.transition
- if (typeof transition === 'function') {
- return transition(to, from)
+ const componentTransitions = component => {
+ const transition = componentOption(component, 'transition', to, from) || {}
+ return (typeof transition === 'string' ? { name: transition } : transition)
+ }
+
+ return Components.map(Component => {
+ // Clone original object to prevent overrides
+ const transitions = Object.assign({}, componentTransitions(Component))
+
+ // Combine transitions & prefer `leave` transitions of 'from' route
+ if (from && from.matched.length && from.matched[0].components.default) {
+ const from_transitions = componentTransitions(from.matched[0].components.default)
+ Object.keys(from_transitions)
+ .filter(key => from_transitions[key] && key.toLowerCase().indexOf('leave') !== -1)
+ .forEach(key => { transitions[key] = from_transitions[key] })
}
- return transition
+
+ return transitions
})
}
-function loadAsyncComponents (to, from, next) {
- const resolveComponents = flatMapComponents(to, (Component, _, match, key) => {
- if (typeof Component === 'function' && !Component.options) {
- return new Promise(function (resolve, reject) {
- const _resolve = (Component) => {
- Component = sanitizeComponent(Component)
- match.components[key] = Component
- resolve(Component)
- }
- Component().then(_resolve).catch(reject)
- })
- }
- Component = sanitizeComponent(Component)
- match.components[key] = Component
- return match.components[key]
- })
- const fromPath = from.fullPath.split('#')[0]
- const toPath = to.fullPath.split('#')[0]
- this._hashChanged = (fromPath === toPath)
- if (!this._hashChanged) {
- <%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
+async function loadAsyncComponents (to, from, next) {
+ // Check if route path changed (this._pathChanged), only if the page is not an error (for validate())
+ this._pathChanged = !!app.nuxt.err || from.path !== to.path
+ this._queryChanged = JSON.stringify(to.query) !== JSON.stringify(from.query)
+ this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : [])
+
+ <% if (loading) { %>
+ if (this._pathChanged && this.$loading.start) {
+ this.$loading.start()
}
- Promise.all(resolveComponents)
- .then(() => next())
- .catch((err) => {
- let statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500
+ <% } %>
+
+ try {
+ const Components = await resolveRouteComponents(to)
+ <% if (loading) { %>
+ if (!this._pathChanged && this._queryChanged) {
+ // Add a marker on each component that it needs to refresh or not
+ const startLoader = Components.some((Component) => {
+ const watchQuery = Component.options.watchQuery
+ if (watchQuery === true) return true
+ if (Array.isArray(watchQuery)) {
+ return watchQuery.some((key) => this._diffQuery[key])
+ }
+ return false
+ })
+ if (startLoader && this.$loading.start) {
+ this.$loading.start()
+ }
+ }
+ <% } %>
+ // Call next()
+ next()
+ } catch (err) {
+ err = err || {}
+ const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500
this.error({ statusCode, message: err.message })
+ this.$nuxt.$emit('routeChanged', to, from, err)
next(false)
+ }
+}
+
+function applySSRData(Component, ssrData) {
+ if (NUXT.serverRendered && ssrData) {
+ applyAsyncData(Component, ssrData)
+ }
+ Component._Ctor = Component
+ return Component
+}
+
+// Get matched components
+function resolveComponents(router) {
+ const path = getLocation(router.options.base, router.options.mode)
+
+ return flatMapComponents(router.match(path), async (Component, _, match, key, index) => {
+ // If component is not resolved yet, resolve it
+ if (typeof Component === 'function' && !Component.options) {
+ Component = await Component()
+ }
+ // Sanitize it and save it
+ const _Component = applySSRData(sanitizeComponent(Component), NUXT.data ? NUXT.data[index] : null)
+ match.components[key] = _Component
+ return _Component
})
}
function callMiddleware (Components, context, layout) {
- // if layout is undefined, only call global middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
let unknownMiddleware = false
+
+ // If layout is undefined, only call global middleware
if (typeof layout !== 'undefined') {
- midd = [] // exclude global middleware if layout defined (already called before)
+ midd = [] // Exclude global middleware if layout defined (already called before)
if (layout.middleware) {
midd = midd.concat(layout.middleware)
}
- Components.forEach((Component) => {
+ Components.forEach(Component => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
}
- midd = midd.map((name) => {
+
+ midd = midd.map(name => {
+ if (typeof name === 'function') return name
if (typeof middleware[name] !== 'function') {
unknownMiddleware = true
this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
+
if (unknownMiddleware) return
return middlewareSeries(midd, context)
}
async function render (to, from, next) {
- if (this._hashChanged) return next()
- let layout
+ if (this._pathChanged === false && this._queryChanged === false) return next()
+
+ // nextCalled is true when redirected
let nextCalled = false
- const _next = function (path) {
- <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
+ const _next = path => {
+ <% if(loading) { %>if(this.$loading.finish) this.$loading.finish()<% } %>
if (nextCalled) return
nextCalled = true
next(path)
}
- let context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }, app)
- let Components = getMatchedComponents(to)
- this._context = context
- this._dateLastError = this.$options._nuxt.dateErr
- this._hadError = !!this.$options._nuxt.err
+
+ // Update context
+ await setContext(app, {
+ route: to,
+ from,
+ next: _next.bind(this)
+ })
+ this._dateLastError = app.nuxt.dateErr
+ this._hadError = !!app.nuxt.err
+
+ // Get route's matched components
+ const Components = getMatchedComponents(to)
+
+ // If no Components matched, generate 404
if (!Components.length) {
// Default layout
- await callMiddleware.call(this, Components, context)
- if (context._redirected) return
- layout = await this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout)
- await callMiddleware.call(this, Components, context, layout)
- if (context._redirected) return
- this.error({ statusCode: 404, message: 'This page could not be found.' })
+ await callMiddleware.call(this, Components, app.context)
+ if (app.context._redirected) return
+ // Load layout for error page
+ const layout = await this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout)
+ await callMiddleware.call(this, Components, app.context, layout)
+ if (app.context._redirected) return
+ // Show error page
+ app.context.error({ statusCode: 404, message: '<%= messages.error_404 %>' })
return next()
}
+
// Update ._data and other properties if hot reloaded
- Components.forEach(function (Component) {
+ Components.forEach(Component => {
if (Component._Ctor && Component._Ctor.options) {
Component.options.asyncData = Component._Ctor.options.asyncData
Component.options.fetch = Component._Ctor.options.fetch
}
})
+
+ // Apply transitions
this.setTransitions(mapTransitions(Components, to, from))
+
try {
+ // Call middleware
+ await callMiddleware.call(this, Components, app.context)
+ if (app.context._redirected) return
+ if (app.context._errored) return next()
+
// Set layout
- await callMiddleware.call(this, Components, context)
- if (context._redirected) return
- layout = Components[0].options.layout
+ let layout = Components[0].options.layout
if (typeof layout === 'function') {
- layout = layout(context)
+ layout = layout(app.context)
}
layout = await this.loadLayout(layout)
- await callMiddleware.call(this, Components, context, layout)
- if (context._redirected) return
- // Pass validation?
+
+ // Call middleware for layout
+ await callMiddleware.call(this, Components, app.context, layout)
+ if (app.context._redirected) return
+ if (app.context._errored) return next()
+
+ // Call .validate()
let isValid = true
- Components.forEach((Component) => {
+ Components.forEach(Component => {
if (!isValid) return
if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({
params: to.params || {},
- query : to.query || {}<%= (store ? ', store: context.store' : '') %>
+ query : to.query || {},
+ <% if(store) { %>store<% } %>
})
})
+ // ...If .validate() returned false
if (!isValid) {
- this.error({ statusCode: 404, message: 'This page could not be found.' })
+ this.error({ statusCode: 404, message: '<%= messages.error_404 %>' })
return next()
}
+
+ // Call asyncData & fetch hooks on components matched by the route.
await Promise.all(Components.map((Component, i) => {
// Check if only children route changed
Component._path = compile(to.matched[i].path)(to.params)
- if (!this._hadError && Component._path === _lastPaths[i] && (i + 1) !== Components.length) {
+ Component._dataRefresh = false
+ // Check if Component need to be refreshed (call asyncData & fetch)
+ // Only if its slug has changed or is watch query changes
+ if (this._pathChanged && Component._path !== _lastPaths[i]) {
+ Component._dataRefresh = true
+ } else if (!this._pathChanged && this._queryChanged) {
+ const watchQuery = Component.options.watchQuery
+ if (watchQuery === true) {
+ Component._dataRefresh = true
+ } else if (Array.isArray(watchQuery)) {
+ Component._dataRefresh = watchQuery.some((key) => this._diffQuery[key])
+ }
+ }
+ if (!this._hadError && this._isMounted && !Component._dataRefresh) {
return Promise.resolve()
}
+
let promises = []
- // asyncData method
- if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
- var promise = promisify(Component.options.asyncData, context)
- promise.then((asyncDataResult) => {
+
+ const hasAsyncData = Component.options.asyncData && typeof Component.options.asyncData === 'function'
+ const hasFetch = !!Component.options.fetch
+ <% if(loading) { %>const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45<% } %>
+
+ // Call asyncData(context)
+ if (hasAsyncData) {
+ const promise = promisify(Component.options.asyncData, app.context)
+ .then(asyncDataResult => {
applyAsyncData(Component, asyncDataResult)
- <%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
+ <% if(loading) { %>if(this.$loading.increase) this.$loading.increase(loadingIncrease)<% } %>
})
promises.push(promise)
}
- if (Component.options.fetch) {
- var p = Component.options.fetch(context)
- if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { p = Promise.resolve(p) }
- <%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
+
+ // Call fetch(context)
+ if (hasFetch) {
+ let p = Component.options.fetch(app.context)
+ if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) {
+ p = Promise.resolve(p)
+ }
+ p.then(fetchResult => {
+ <% if(loading) { %>if(this.$loading.increase) this.$loading.increase(loadingIncrease)<% } %>
+ })
promises.push(p)
}
+
return Promise.all(promises)
}))
+
_lastPaths = Components.map((Component, i) => compile(to.matched[i].path)(to.params))
- <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
+
+ <% if(loading) { %>if(this.$loading.finish) this.$loading.finish()<% } %>
+
// If not redirected
- if (!nextCalled) {
- next()
- }
+ if (!nextCalled) next()
+
} catch (error) {
+ if (!error) error = {}
_lastPaths = []
error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
+
+ // Load error layout
let layout = NuxtError.layout
if (typeof layout === 'function') {
- layout = layout(context)
+ layout = layout(app.context)
}
- this.loadLayout(layout)
- .then(() => {
- this.error(error)
- next(false)
- })
+ await this.loadLayout(layout)
+
+ this.error(error)
+ this.$nuxt.$emit('routeChanged', to, from, error)
+ next(false)
}
}
@@ -196,66 +365,92 @@ function normalizeComponents (to, ___) {
})
}
+function showNextPage(to) {
+ // Hide error component if no error
+ if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) {
+ this.error()
+ }
+
+ // Set layout
+ let layout = this.$options.nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout
+ if (typeof layout === 'function') {
+ layout = layout(app.context)
+ }
+ this.setLayout(layout)
+}
+
// When navigating on a different route but the same component is used, Vue.js
-// will not update the instance data, so we have to update $data ourselves
-function fixPrepatch (to, ___) {
- if (this._hashChanged) return
+// Will not update the instance data, so we have to update $data ourselves
+function fixPrepatch(to, ___) {
+ if (this._pathChanged === false && this._queryChanged === false) return
+
Vue.nextTick(() => {
- let instances = getMatchedComponentsInstances(to)
- _lastComponentsFiles = instances.map((instance, i) => {
- if (!instance) return '';
- if (_lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
- let newData = instance.constructor.options.data.call(instance)
+ const instances = getMatchedComponentsInstances(to)
+
+ instances.forEach((instance, i) => {
+ if (!instance) return
+ if (instance.constructor._dataRefresh && _lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
+ const newData = instance.constructor.options.data.call(instance)
for (let key in newData) {
Vue.set(instance.$data, key, newData[key])
}
}
- return instance.constructor.options.__file
})
- // hide error component if no error
- if (this._hadError && this._dateLastError === this.$options._nuxt.dateErr) {
- this.error()
- }
- // Set layout
- let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout
- if (typeof layout === 'function') {
- layout = layout(this._context)
- }
- this.setLayout(layout)
- // hot reloading
+ showNextPage.call(this, to)
+ <% if (isDev) { %>
+ // Hot reloading
setTimeout(() => hotReloadAPI(this), 100)
+ <% } %>
})
}
+function nuxtReady (_app) {
+ window._nuxtReadyCbs.forEach((cb) => {
+ if (typeof cb === 'function') {
+ cb(_app)
+ }
+ })
+ // Special JSDOM
+ if (typeof window._onNuxtLoaded === 'function') {
+ window._onNuxtLoaded(_app)
+ }
+ // Add router hooks
+ router.afterEach(function (to, from) {
+ // Wait for fixPrepatch + $data updates
+ Vue.nextTick(() => _app.$nuxt.$emit('routeChanged', to, from))
+ })
+}
+
+<% if (isDev) { %>
// Special hot reload with asyncData(context)
+function getNuxtChildComponents($parent, $components = []) {
+ $parent.$children.forEach(($child) => {
+ if ($child.$vnode.data.nuxtChild && !$components.find(c =>(c.$options.__file === $child.$options.__file))) {
+ $components.push($child)
+ }
+ if ($child.$children && $child.$children.length) {
+ getNuxtChildComponents($child, $components)
+ }
+ })
+
+ return $components
+}
+
function hotReloadAPI (_app) {
if (!module.hot) return
- let $components = []
- let $nuxt = _app.$nuxt
- while ($nuxt && $nuxt.$children && $nuxt.$children.length) {
- $nuxt.$children.forEach(function (child, i) {
- if (child.$vnode.data.nuxtChild) {
- let hasAlready = false
- $components.forEach(function (component) {
- if (component.$options.__file === child.$options.__file) {
- hasAlready = true
- }
- })
- if (!hasAlready) {
- $components.push(child)
- }
- }
- $nuxt = child
- })
- }
+
+ let $components = getNuxtChildComponents(_app.$nuxt, [])
+
$components.forEach(addHotReload.bind(_app))
}
function addHotReload ($component, depth) {
if ($component.$vnode.data._hasHotReload) return
$component.$vnode.data._hasHotReload = true
+
var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
- $component.$vnode.context.$forceUpdate = () => {
+
+ $component.$vnode.context.$forceUpdate = async () => {
let Components = getMatchedComponents(router.currentRoute)
let Component = Components[depth]
if (!Component) return _forceUpdate()
@@ -270,7 +465,12 @@ function addHotReload ($component, depth) {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
router.push(path)
}
- let context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, hotReload: true, next: next.bind(this), error: this.error }, app)
+ await setContext(app, {
+ route: router.currentRoute,
+ isHMR: true,
+ next: next.bind(this)
+ })
+ const context = app.context
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
callMiddleware.call(this, Components, context)
.then(() => {
@@ -314,104 +514,77 @@ function addHotReload ($component, depth) {
})
}
}
+<% } %>
-// Load vue app
-const NUXT = window.__NUXT__ || {}
-if (!NUXT) {
- throw new Error('[nuxt.js] cannot find the global variable __NUXT__, make sure the server is working.')
-}
-// Get matched components
-const resolveComponents = function (router) {
- const path = getLocation(router.options.base)
- return flatMapComponents(router.match(path), (Component, _, match, key, index) => {
- if (typeof Component === 'function' && !Component.options) {
- return new Promise(function (resolve, reject) {
- const _resolve = (Component) => {
- Component = sanitizeComponent(Component)
- if (NUXT.serverRendered) {
- applyAsyncData(Component, NUXT.data[index])
- }
- match.components[key] = Component
- resolve(Component)
- }
- Component().then(_resolve).catch(reject)
- })
- }
- Component = sanitizeComponent(Component)
- match.components[key] = Component
- return Component
- })
-}
-
-function nuxtReady (app) {
- window._nuxtReadyCbs.forEach((cb) => {
- if (typeof cb === 'function') {
- cb(app)
- }
- })
- // Special JSDOM
- if (typeof window._onNuxtLoaded === 'function') {
- window._onNuxtLoaded(app)
- }
- // Add router hooks
- router.afterEach(function (to, from) {
- app.$nuxt.$emit('routeChanged', to, from)
- })
-}
-
-createApp()
-.then(async (__app) => {
+async function mountApp(__app) {
+ // Set global variables
app = __app.app
router = __app.router
- <%= (store ? 'store = __app.store' : '') %>
+ <% if (store) { %>store = __app.store <% } %>
+
+ // Resolve route components
const Components = await Promise.all(resolveComponents(router))
+
+ // Create Vue instance
const _app = new Vue(app)
+
+ <% if (mode !== 'spa') { %>
+ // Load layout
const layout = NUXT.layout || 'default'
await _app.loadLayout(layout)
_app.setLayout(layout)
- const mountApp = () => {
+ <% } %>
+
+ // Mounts Vue app to DOM element
+ const mount = () => {
_app.$mount('#__nuxt')
+
+ // Listen for first Vue update
Vue.nextTick(() => {
- // Hot reloading
- hotReloadAPI(_app)
// Call window.onNuxtReady callbacks
nuxtReady(_app)
+ <% if (isDev) { %>
+ // Enable hot reloading
+ hotReloadAPI(_app)
+ <% } %>
})
}
- _app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app)
+
+ // Enable transitions
+ _app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app)
if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute))
- _lastPaths = router.currentRoute.matched.map((route) => compile(route.path)(router.currentRoute.params))
- _lastComponentsFiles = Components.map((Component) => Component.options.__file)
+ _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
}
- _app.error = _app.$options._nuxt.error.bind(_app)
- _app.$loading = {} // to avoid error while _app.$nuxt does not exist
+
+ // Initialize error handler
+ _app.$loading = {} // To avoid error while _app.$nuxt does not exist
if (NUXT.error) _app.error(NUXT.error)
+
// Add router hooks
router.beforeEach(loadAsyncComponents.bind(_app))
router.beforeEach(render.bind(_app))
router.afterEach(normalizeComponents)
router.afterEach(fixPrepatch.bind(_app))
+
+ // If page already is server rendered
if (NUXT.serverRendered) {
- mountApp()
+ mount()
return
}
- render.call(_app, router.currentRoute, router.currentRoute, function (path) {
- if (path) {
- let mounted = false
- router.afterEach(function () {
- if (mounted) return
- mounted = true
- mountApp()
- })
- router.push(path)
+
+ // First render on client-side
+ render.call(_app, router.currentRoute, router.currentRoute, (path) => {
+ // If not redirected
+ if (!path) {
+ normalizeComponents(router.currentRoute, router.currentRoute)
+ showNextPage.call(_app, router.currentRoute)
+ // Dont call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render
+ mount()
return
}
- normalizeComponents(router.currentRoute, router.currentRoute)
- fixPrepatch.call(_app, router.currentRoute, router.currentRoute)
- mountApp()
+
+ // Push the path and then mount app
+ router.push(path, () => mount(), (err) => console.error(err))
})
-})
-.catch((err) => {
- console.error('[nuxt.js] Cannot load components', err) // eslint-disable-line no-console
-})
+}
diff --git a/lib/app/components/no-ssr.js b/lib/app/components/no-ssr.js
new file mode 100644
index 0000000000..9dbc5bcbf5
--- /dev/null
+++ b/lib/app/components/no-ssr.js
@@ -0,0 +1,28 @@
+/*
+** From https://github.com/egoist/vue-no-ssr
+** With the authorization of @egoist
+*/
+export default {
+ name: 'no-ssr',
+ props: ['placeholder'],
+ data () {
+ return {
+ canRender: false
+ }
+ },
+ mounted () {
+ this.canRender = true
+ },
+ render (h) {
+ if (this.canRender) {
+ if (
+ process.env.NODE_ENV === 'development' &&
+ this.$slots.default.length > 1
+ ) {
+ throw new Error('
You cannot use multiple child components')
+ }
+ return this.$slots.default[0]
+ }
+ return h('div', { class: { 'no-ssr-placeholder': true } }, this.placeholder)
+ }
+}
diff --git a/lib/app/components/nuxt-child.js b/lib/app/components/nuxt-child.js
index becc3b82a8..52b5c2a78b 100644
--- a/lib/app/components/nuxt-child.js
+++ b/lib/app/components/nuxt-child.js
@@ -1,38 +1,12 @@
-import Vue from 'vue'
-
-const transitionsKeys = [
- 'name',
- 'mode',
- 'css',
- 'type',
- 'duration',
- 'enterClass',
- 'leaveClass',
- 'enterActiveClass',
- 'enterActiveClass',
- 'leaveActiveClass',
- 'enterToClass',
- 'leaveToClass'
-]
-const listenersKeys = [
- 'beforeEnter',
- 'enter',
- 'afterEnter',
- 'enterCancelled',
- 'beforeLeave',
- 'leave',
- 'afterLeave',
- 'leaveCancelled'
-]
-
export default {
name: 'nuxt-child',
functional: true,
render (h, { parent, data }) {
data.nuxtChild = true
-
+ const _parent = parent
const transitions = parent.$nuxt.nuxt.transitions
const defaultTransition = parent.$nuxt.nuxt.defaultTransition
+
let depth = 0
while (parent) {
if (parent.$vnode && parent.$vnode.data.nuxtChild) {
@@ -51,9 +25,10 @@ export default {
let listeners = {}
listenersKeys.forEach((key) => {
if (typeof transition[key] === 'function') {
- listeners[key] = transition[key]
+ listeners[key] = transition[key].bind(_parent)
}
})
+
return h('transition', {
props: transitionProps,
on: listeners
@@ -62,3 +37,37 @@ export default {
])
}
}
+
+const transitionsKeys = [
+ 'name',
+ 'mode',
+ 'appear',
+ 'css',
+ 'type',
+ 'duration',
+ 'enterClass',
+ 'leaveClass',
+ 'appearClass',
+ 'enterActiveClass',
+ 'enterActiveClass',
+ 'leaveActiveClass',
+ 'appearActiveClass',
+ 'enterToClass',
+ 'leaveToClass',
+ 'appearToClass'
+]
+
+const listenersKeys = [
+ 'beforeEnter',
+ 'enter',
+ 'afterEnter',
+ 'enterCancelled',
+ 'beforeLeave',
+ 'leave',
+ 'afterLeave',
+ 'leaveCancelled',
+ 'beforeAppear',
+ 'appear',
+ 'afterAppear',
+ 'appearCancelled'
+]
diff --git a/lib/app/components/nuxt-error.vue b/lib/app/components/nuxt-error.vue
index 476d67d441..a304f59e85 100644
--- a/lib/app/components/nuxt-error.vue
+++ b/lib/app/components/nuxt-error.vue
@@ -1,13 +1,21 @@
-
-
-
{{ error.statusCode }}
-
-
{{ error.message }}
-
-
Back to the home page
-
+
+
+
+
+
{{ message }}
+
+ <%= messages.back_to_home %>
+
+ <% if(debug) { %>
+
<%= messages.client_error_details %>
+ <% } %>
+
+
+
+
-
diff --git a/lib/app/components/nuxt-link.js b/lib/app/components/nuxt-link.js
index c23fdd7ac3..fea5f308ce 100644
--- a/lib/app/components/nuxt-link.js
+++ b/lib/app/components/nuxt-link.js
@@ -1,5 +1,3 @@
-import Vue from 'vue'
-
export default {
name: 'nuxt-link',
functional: true,
diff --git a/lib/app/components/nuxt-loading.vue b/lib/app/components/nuxt-loading.vue
index f7d0e91cae..349a1e60e0 100644
--- a/lib/app/components/nuxt-loading.vue
+++ b/lib/app/components/nuxt-loading.vue
@@ -1,5 +1,5 @@
- '
+import { compile } from '../utils'
+
+export default {
+ name: 'nuxt',
+ props: ['nuxtChildKey'],
+ render(h) {
+ // If there is some error
+ if (this.nuxt.err) {
+ return h('nuxt-error', {
+ props: {
+ error: this.nuxt.err
+ }
+ })
+ }
+ // Directly return nuxt child
+ return h('nuxt-child', {
+ key: this.routerViewKey
+ })
+ },
+ beforeCreate () {
+ Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt)
+ },
+ computed: {
+ routerViewKey () {
+ // If nuxtChildKey prop is given or current route has children
+ if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) {
+ return this.nuxtChildKey || compile(this.$route.matched[0].path)(this.$route.params)
+ }
+ const Component = this.$route.matched[0] && this.$route.matched[0].components.default
+ if (Component && Component.options && Component.options.key) {
+ return (typeof Component.options.key === 'function' ? Component.options.key(this.$route) : Component.options.key)
+ }
+ return this.$route.path
+ }
+ },
+ components: {
+ NuxtChild,
+ NuxtError
+ }
+}
diff --git a/lib/app/components/nuxt.vue b/lib/app/components/nuxt.vue
deleted file mode 100644
index b6eb339a31..0000000000
--- a/lib/app/components/nuxt.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
diff --git a/lib/app/empty.js b/lib/app/empty.js
new file mode 100644
index 0000000000..a3ac0d8486
--- /dev/null
+++ b/lib/app/empty.js
@@ -0,0 +1 @@
+// This file is intentionally left empty for noop aliases
diff --git a/lib/app/index.js b/lib/app/index.js
index 0b556cdf82..986ad113ec 100644
--- a/lib/app/index.js
+++ b/lib/app/index.js
@@ -1,37 +1,31 @@
-'use strict'
-
+import 'es6-promise/auto'
import Vue from 'vue'
import Meta from 'vue-meta'
import { createRouter } from './router.js'
-<% if (store) { %>import { createStore } from './store.js'<% } %>
+import NoSSR from './components/no-ssr.js'
import NuxtChild from './components/nuxt-child.js'
import NuxtLink from './components/nuxt-link.js'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
-import Nuxt from './components/nuxt.vue'
+import Nuxt from './components/nuxt.js'
import App from '<%= appPath %>'
+import { setContext, getLocation } from './utils'
+<% if (store) { %>import { createStore } from './store.js'<% } %>
-import { getContext } from './utils'
+/* Plugins */
-if (process.browser) {
- // window.onNuxtReady(() => console.log('Ready')) hook
- // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
- window._nuxtReadyCbs = []
- window.onNuxtReady = function (cb) {
- window._nuxtReadyCbs.push(cb)
- }
-}
+<% plugins.forEach(plugin => { %>// <%= plugin.src %><%= (plugin.ssr===false) ? ' (Only included in client bundle)' : '' %>
+import <%= plugin.name %> from '<%= plugin.name %>'<% }) %>
-// Import SSR plugins
-<% plugins.forEach(function (plugin) { if (plugin.ssr)
-{ %>let <%= plugin.name %> = require('<%= r(plugin.src) %>')
-<%= plugin.name %> = <%= plugin.name %>.default || <%= plugin.name %>
-<% }}) %>
+// Component:
+Vue.component(NoSSR.name, NoSSR)
// Component:
Vue.component(NuxtChild.name, NuxtChild)
+
// Component:
Vue.component(NuxtLink.name, NuxtLink)
-// Component:
+
+// Component: `
Vue.component(Nuxt.name, Nuxt)
// vue-meta configuration
@@ -46,38 +40,27 @@ const defaultTransition = <%=
serialize(transition)
.replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(')
.replace('enterCancelled(', 'function(').replace('beforeLeave(', 'function(').replace('leave(', 'function(')
- .replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(')
- %>
+ .replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(').replace('beforeAppear(', 'function(')
+ .replace('appear(', 'function(').replace('afterAppear(', 'function(').replace('appearCancelled(', 'function(')
+%>
async function createApp (ssrContext) {
- <% if (store) { %>
- const store = createStore()
- <% } %>
const router = createRouter()
- if (process.server && ssrContext && ssrContext.url) {
- await new Promise((resolve, reject) => {
- router.push(ssrContext.url, resolve, reject)
- })
- }
+ <% if (store) { %>
+ const store = createStore()
+ // Add this.$router into store actions/mutations
+ store.$router = router
+ <% } %>
- if (process.browser) {
- <% if (store) { %>
- // Replace store state before calling plugins
- if (window.__NUXT__ && window.__NUXT__.state) {
- store.replaceState(window.__NUXT__.state)
- }
- <% } %>
- }
-
- // root instance
+ // Create Root instance
// here we inject the router and store to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
- let app = {
+ const app = {
router,
- <%= (store ? 'store,' : '') %>
- _nuxt: {
- defaultTransition: defaultTransition,
+ <% if (store) { %>store,<% } %>
+ nuxt: {
+ defaultTransition,
transitions: [ defaultTransition ],
setTransitions (transitions) {
if (!Array.isArray(transitions)) {
@@ -93,54 +76,106 @@ async function createApp (ssrContext) {
}
return transition
})
- this.$options._nuxt.transitions = transitions
+ this.$options.nuxt.transitions = transitions
return transitions
},
err: null,
dateErr: null,
error (err) {
err = err || null
- if (typeof err === 'string') {
- err = { statusCode: 500, message: err }
- }
- this.$options._nuxt.dateErr = Date.now()
- this.$options._nuxt.err = err;
+ app.context._errored = !!err
+ if (typeof err === 'string') err = { statusCode: 500, message: err }
+ const nuxt = this.nuxt || this.$options.nuxt
+ nuxt.dateErr = Date.now()
+ nuxt.err = err
+ // Used in lib/server.js
+ if (ssrContext) ssrContext.nuxt.error = err
return err
}
},
...App
}
+ <% if (store) { %>
+ // Make app available into store via this.app
+ store.app = app
+ <% } %>
+ const next = ssrContext ? ssrContext.next : location => app.router.push(location)
+ // Resolve route
+ let route
+ if (ssrContext) {
+ route = router.resolve(ssrContext.url).route
+ } else {
+ const path = getLocation(router.options.base)
+ route = router.resolve(path).route
+ }
- const next = ssrContext ? ssrContext.next : (location) => app.router.push(location)
- const ctx = getContext({
- isServer: !!ssrContext,
- isClient: !ssrContext,
- route: router.currentRoute,
+ // Set context to app.context
+ await setContext(app, {
+ route,
next,
- <%= (store ? 'store,' : '') %>
+ error: app.nuxt.error.bind(app),
+ <% if (store) { %>store,<% } %>
+ payload: ssrContext ? ssrContext.payload : undefined,
req: ssrContext ? ssrContext.req : undefined,
res: ssrContext ? ssrContext.res : undefined,
- }, app)
- delete ctx.error
+ beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined
+ })
- // Inject external plugins
- <% plugins.forEach(function (plugin) {
- if (plugin.ssr) { %>
- if (typeof <%= plugin.name %> === 'function') {
- await <%= plugin.name %>(ctx)
+ const inject = function (key, value) {
+ if (!key) throw new Error('inject(key, value) has no key provided')
+ if (!value) throw new Error('inject(key, value) has no value provided')
+ key = '$' + key
+ // Add into app
+ app[key] = value
+ // Check if plugin not already installed
+ const installKey = '__nuxt_' + key + '_installed__'
+ if (Vue[installKey]) return
+ Vue[installKey] = true
+ // Call Vue.use() to install the plugin into vm
+ Vue.use(() => {
+ if (!Vue.prototype.hasOwnProperty(key)) {
+ Object.defineProperty(Vue.prototype, key, {
+ get () {
+ return this.$root.$options[key]
+ }
+ })
+ }
+ })
+ <% if (store) { %>
+ // Add into store
+ store[key] = app[key]
+ <% } %>
}
- <% } else { %>
+
+ <% if (store) { %>
if (process.browser) {
- let <%= plugin.name %> = require('<%= plugin.src %>')
- <%= plugin.name %> = <%= plugin.name %>.default || <%= plugin.name %>
- if (typeof <%= plugin.name %> === 'function') {
- await <%= plugin.name %>(ctx)
+ // Replace store state before plugins execution
+ if (window.__NUXT__ && window.__NUXT__.state) {
+ store.replaceState(window.__NUXT__.state)
}
}
- <% }
- }) %>
+ <% } %>
- return { app, router<%= (store ? ', store' : '') %> }
+ // Plugin execution
+ <% plugins.filter(p => p.ssr).forEach(plugin => { %>
+ if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(app.context, inject)<% }) %>
+ <% if (plugins.filter(p => !p.ssr).length) { %>
+ if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %>
+ if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(app.context, inject)<% }) %>
+ }<% } %>
+
+ // If server-side, wait for async component to be resolved first
+ if (process.server && ssrContext && ssrContext.url) {
+ await new Promise((resolve, reject) => {
+ router.push(ssrContext.url, resolve, reject)
+ })
+ }
+
+ return {
+ app,
+ router,
+ <% if(store) { %>store<% } %>
+ }
}
export { createApp, NuxtError }
diff --git a/lib/app/middleware.js b/lib/app/middleware.js
index 4ef5b98954..57e04073cb 100644
--- a/lib/app/middleware.js
+++ b/lib/app/middleware.js
@@ -1,5 +1,5 @@
<% if (middleware) { %>
-let files = require.context('~/middleware', false, /^\.\/.*\.(js|ts)$/)
+let files = require.context('@/middleware', false, /^\.\/.*\.(js|ts)$/)
let filenames = files.keys()
function getModule (filename) {
diff --git a/lib/app/router.js b/lib/app/router.js
index 735b6cf2ec..efad4256db 100644
--- a/lib/app/router.js
+++ b/lib/app/router.js
@@ -1,5 +1,3 @@
-'use strict'
-
import Vue from 'vue'
import Router from 'vue-router'
@@ -7,10 +5,10 @@ Vue.use(Router)
<%
function recursiveRoutes(routes, tab, components) {
- var res = ''
+ let res = ''
routes.forEach((route, i) => {
route._name = '_' + hash(route.component)
- components.push({ _name: route._name, component: route.component, name: route.name })
+ components.push({ _name: route._name, component: route.component, name: route.name, chunkName: route.chunkName })
res += tab + '{\n'
res += tab + '\tpath: ' + JSON.stringify(route.path) + ',\n'
res += tab + '\tcomponent: ' + route._name
@@ -20,31 +18,30 @@ function recursiveRoutes(routes, tab, components) {
})
return res
}
-var _components = []
-var _routes = recursiveRoutes(router.routes, '\t\t', _components)
-uniqBy(_components, '_name').forEach((route) => { %>
-const <%= route._name %> = () => import('<%= route.component %>' /* webpackChunkName: "pages/<%= route.name %>" */)
+const _components = []
+const _routes = recursiveRoutes(router.routes, '\t\t', _components)
+uniqBy(_components, '_name').forEach((route) => { %>const <%= route._name %> = () => import('<%= relativeToBuild(route.component) %>' /* webpackChunkName: "<%= wChunk(route.chunkName) %>" */).then(m => m.default || m)
<% }) %>
<% if (router.scrollBehavior) { %>
-const scrollBehavior = <%= serialize(router.scrollBehavior).replace('scrollBehavior(', 'function(') %>
+const scrollBehavior = <%= serialize(router.scrollBehavior).replace('scrollBehavior(', 'function(').replace('function function', 'function') %>
<% } else { %>
const scrollBehavior = (to, from, savedPosition) => {
- // savedPosition is only available for popstate navigations.
+ // SavedPosition is only available for popstate navigations.
if (savedPosition) {
return savedPosition
} else {
let position = {}
- // if no children detected
+ // If no children detected
if (to.matched.length < 2) {
- // scroll to the top of the page
+ // Scroll to the top of the page
position = { x: 0, y: 0 }
}
else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
- // if one of the children has scrollToTop option set to true
+ // If one of the children has scrollToTop option set to true
position = { x: 0, y: 0 }
}
- // if link has anchor, scroll to anchor by returning the selector
+ // If link has anchor, scroll to anchor by returning the selector
if (to.hash) {
position = { selector: to.hash }
}
@@ -61,7 +58,10 @@ export function createRouter () {
linkExactActiveClass: '<%= router.linkExactActiveClass %>',
scrollBehavior,
routes: [
- <%= _routes %>
- ]
+<%= _routes %>
+ ],
+ <% if (router.parseQuery) { %>parseQuery: <%= serialize(router.parseQuery).replace('parseQuery(', 'function(') %>,<% } %>
+ <% if (router.stringifyQuery) { %>stringifyQuery: <%= serialize(router.stringifyQuery).replace('stringifyQuery(', 'function(') %>,<% } %>
+ fallback: <%= router.fallback %>
})
}
diff --git a/lib/app/server.js b/lib/app/server.js
index 326fa3dfd6..2322298049 100644
--- a/lib/app/server.js
+++ b/lib/app/server.js
@@ -1,99 +1,137 @@
-'use strict'
-
import Vue from 'vue'
+import clone from 'clone'
import { stringify } from 'querystring'
import { omit } from 'lodash'
import middleware from './middleware'
import { createApp, NuxtError } from './index'
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
+
const debug = require('debug')('nuxt:render')
debug.color = 4 // force blue color
const isDev = <%= isDev %>
+const noopApp = () => new Vue({ render: (h) => h('div') })
+
+const createNext = ssrContext => opts => {
+ ssrContext.redirected = opts
+ // If nuxt generate
+ if (!ssrContext.res) {
+ ssrContext.nuxt.serverRendered = false
+ return
+ }
+ opts.query = stringify(opts.query)
+ opts.path = opts.path + (opts.query ? '?' + opts.query : '')
+ if (opts.path.indexOf('http') !== 0 && ('<%= router.base %>' !== '/' && opts.path.indexOf('<%= router.base %>') !== 0)) {
+ opts.path = urlJoin('<%= router.base %>', opts.path)
+ }
+ // Avoid loop redirect
+ if (opts.path === ssrContext.url) {
+ ssrContext.redirected = false
+ return
+ }
+ ssrContext.res.writeHead(opts.status, {
+ 'Location': opts.path
+ })
+ ssrContext.res.end()
+}
+
// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
-export default async (context) => {
- // create context.next for simulate next() of beforeEach() when wanted to redirect
- context.redirected = false
- context.next = function (opts) {
- context.redirected = opts
- // if nuxt generate
- if (!context.res) {
- context.nuxt.serverRendered = false
- return
- }
- opts.query = stringify(opts.query)
- opts.path = opts.path + (opts.query ? '?' + opts.query : '')
- if (opts.path.indexOf('http') !== 0 && ('<%= router.base %>' !== '/' && opts.path.indexOf('<%= router.base %>') !== 0)) {
- opts.path = urlJoin('<%= router.base %>', opts.path)
- }
- context.res.writeHead(opts.status, {
- 'Location': opts.path
- })
- context.res.end()
- }
- const { app, router<%= (store ? ', store' : '') %> } = await createApp(context)
- const _app = new Vue(app)
- const _noopApp = new Vue({ render: (h) => h('div') })
- // Add store to the context
- <%= (store ? 'context.store = store' : '') %>
- // Add route to the context
- context.route = router.currentRoute
- // Nuxt object
- context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
- // Add meta infos
- context.meta = _app.$meta()
- // Error function
- context.error = _app.$options._nuxt.error.bind(_app)
+export default async ssrContext => {
+ // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect
+ ssrContext.redirected = false
+ ssrContext.next = createNext(ssrContext)
+ // Used for beforeNuxtRender({ Components, nuxtState })
+ ssrContext.beforeRenderFns = []
- <%= (isDev ? 'const s = isDev && Date.now()' : '') %>
- let ctx = getContext(context, app)
- let Components = []
- let promises = getMatchedComponents(router.match(context.url)).map((Component) => {
- return new Promise((resolve, reject) => {
- if (typeof Component !== 'function' || Component.super === Vue) return resolve(sanitizeComponent(Component))
- const _resolve = (Component) => resolve(sanitizeComponent(Component))
- Component().then(_resolve).catch(reject)
- })
- })
- try {
- Components = await Promise.all(promises)
- } catch (err) {
- // Throw back error to renderRoute()
- throw err
+ // Create the app definition and the instance (created for each request)
+ const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
+ const _app = new Vue(app)
+
+ // Nuxt object (window.__NUXT__)
+ ssrContext.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
+ // Add meta infos (used in renderer.js)
+ ssrContext.meta = _app.$meta()
+ // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext)
+ ssrContext.asyncData = {}
+
+ const beforeRender = async () => {
+ // Call beforeNuxtRender() methods
+ await Promise.all(ssrContext.beforeRenderFns.map((fn) => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
+ <% if (store) { %>
+ // Add the state from the vuex store
+ ssrContext.nuxt.state = store.state
+ <% } %>
}
- // nuxtServerInit
+ const renderErrorPage = async () => {
+ // Load layout for error page
+ let errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout)
+ ssrContext.nuxt.layout = errLayout || 'default'
+ await _app.loadLayout(errLayout)
+ _app.setLayout(errLayout)
+ await beforeRender()
+ return _app
+ }
+ const render404Page = async () => {
+ app.context.error({ statusCode: 404, message: '<%= messages.error_404 %>' })
+ return await renderErrorPage()
+ }
+
+ <% if (isDev) { %>const s = isDev && Date.now()<% } %>
+
+ // Components are already resolved by setContext -> getRouteData (app/utils.js)
+ const Components = getMatchedComponents(router.match(ssrContext.url))
+
<% if (store) { %>
- let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context, app), 'redirect', 'error')) : null)
- if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) promise = Promise.resolve()
- <% } else { %>
- let promise = Promise.resolve()
+ /*
+ ** Dispatch store nuxtServerInit
+ */
+ if (store._actions && store._actions.nuxtServerInit) {
+ try {
+ await store.dispatch('nuxtServerInit', app.context)
+ } catch (err) {
+ debug('error occurred when calling nuxtServerInit: ', err.message)
+ throw err
+ }
+ }
+ // ...If there is a redirect or an error, stop the process
+ if (ssrContext.redirected) return noopApp()
+ if (ssrContext.nuxt.error) return await renderErrorPage()
<% } %>
- await promise
- // Call global middleware (nuxt.config.js)
- let midd = <%= serialize(router.middleware, { isJSON: true }) %>
+
+ /*
+ ** Call global middleware (nuxt.config.js)
+ */
+ let midd = <%= serialize(router.middleware).replace('middleware(', 'function(') %>
midd = midd.map((name) => {
+ if (typeof name === 'function') return name
if (typeof middleware[name] !== 'function') {
- context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
+ ssrContext.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
- if (!context.nuxt.error) {
- await middlewareSeries(midd, ctx)
- }
- if (context.redirected) return _noopApp
- // Set layout
+ await middlewareSeries(midd, app.context)
+ // ...If there is a redirect or an error, stop the process
+ if (ssrContext.redirected) return noopApp()
+ if (ssrContext.nuxt.error) return await renderErrorPage()
+
+ /*
+ ** Set layout
+ */
let layout = Components.length ? Components[0].options.layout : NuxtError.layout
- if (typeof layout === 'function') layout = layout(ctx)
+ if (typeof layout === 'function') layout = layout(app.context)
await _app.loadLayout(layout)
layout = _app.setLayout(layout)
- // Set layout to __NUXT__
- context.nuxt.layout = _app.layoutName
- // Call middleware (layout + pages)
+ // ...Set layout to __NUXT__
+ ssrContext.nuxt.layout = _app.layoutName
+
+ /*
+ ** Call middleware (layout + pages)
+ */
midd = []
if (layout.middleware) midd = midd.concat(layout.middleware)
Components.forEach((Component) => {
@@ -102,80 +140,80 @@ export default async (context) => {
}
})
midd = midd.map((name) => {
+ if (typeof name === 'function') return name
if (typeof middleware[name] !== 'function') {
- context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
+ app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
- if (!context.nuxt.error) {
- await middlewareSeries(midd, ctx)
- }
- if (context.redirected) return _noopApp
- // Call .validate()
+ await middlewareSeries(midd, app.context)
+ // ...If there is a redirect or an error, stop the process
+ if (ssrContext.redirected) return noopApp()
+ if (ssrContext.nuxt.error) return await renderErrorPage()
+
+ /*
+ ** Call .validate()
+ */
let isValid = true
Components.forEach((Component) => {
if (!isValid) return
if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({
- params: context.route.params || {},
- query: context.route.query || {}<%= (store ? ', store: ctx.store' : '') %>
+ params: app.context.route.params || {},
+ query: app.context.route.query || {},
+ <%= (store ? 'store' : '') %>
})
})
- // If .validate() returned false
+ // ...If .validate() returned false
if (!isValid) {
// Don't server-render the page in generate mode
- if (context._generate) {
- context.nuxt.serverRendered = false
- }
- // Call the 404 error by making the Components array empty
- Components = []
+ if (ssrContext._generate) ssrContext.nuxt.serverRendered = false
+ // Render a 404 error page
+ return render404Page()
}
+
+ // If no Components found, returns 404
+ if (!Components.length) return render404Page()
+
// Call asyncData & fetch hooks on components matched by the route.
- let asyncDatas = await Promise.all(Components.map((Component) => {
+ let asyncDatas = await Promise.all(Components.map(Component => {
let promises = []
+
+ // Call asyncData(context)
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
- let promise = promisify(Component.options.asyncData, ctx)
- // Call asyncData(context)
- promise.then((asyncDataResult) => {
- applyAsyncData(Component, asyncDataResult)
+ let promise = promisify(Component.options.asyncData, app.context)
+ promise.then(asyncDataResult => {
+ ssrContext.asyncData[Component.cid] = asyncDataResult
+ applyAsyncData(Component)
return asyncDataResult
})
promises.push(promise)
- } else promises.push(null)
- // call fetch(context)
- if (Component.options.fetch) promises.push(Component.options.fetch(ctx))
- else promises.push(null)
+ } else {
+ promises.push(null)
+ }
+
+ // Call fetch(context)
+ if (Component.options.fetch) {
+ promises.push(Component.options.fetch(app.context))
+ }
+ else {
+ promises.push(null)
+ }
+
return Promise.all(promises)
}))
- // If no Components found, returns 404
- if (!Components.length) {
- context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.' })
- }
- <% if (isDev) { %>
- if (asyncDatas.length) debug('Data fetching ' + context.url + ': ' + (Date.now() - s) + 'ms')
- <% } %>
+
+ <% if (isDev) { %>if (asyncDatas.length) debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms')<% } %>
+
// datas are the first row of each
- context.nuxt.data = asyncDatas.map((r) => (r[0] || {}))
- // If an error occured in the execution
- if (_app.$options._nuxt.err) {
- context.nuxt.error = _app.$options._nuxt.err
- }
- <%= (store ? '// Add the state from the vuex store' : '') %>
- <%= (store ? 'context.nuxt.state = store.state' : '') %>
- // If no error, return main app
- if (!context.nuxt.error) {
- return _app
- }
- // Load layout for error page
- layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout)
- context.nuxt.layout = layout || ''
- await _app.loadLayout(layout)
- _app.setLayout(layout)
+ ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {})
+
+ // ...If there is a redirect or an error, stop the process
+ if (ssrContext.redirected) return noopApp()
+ if (ssrContext.nuxt.error) return await renderErrorPage()
+
+ // Call beforeNuxtRender methods & add store state
+ await beforeRender()
+
return _app
- // if (typeof error === 'string') {
- // error = { statusCode: 500, message: error }
- // }
- // context.nuxt.error = context.error(error)
- // <%= (store ? 'context.nuxt.state = store.state' : '') %>
- // return _app
}
diff --git a/lib/app/store.js b/lib/app/store.js
index 4d12f9ecef..d1e59efcd6 100644
--- a/lib/app/store.js
+++ b/lib/app/store.js
@@ -3,15 +3,20 @@ import Vuex from 'vuex'
Vue.use(Vuex)
-// Recursive find files in ~/store
-const files = require.context('~/store', true, /^\.\/.*\.(js|ts)$/)
+// Recursive find files in {srcDir}/store
+const files = require.context('@/store', true, /^\.\/.*\.(js|ts)$/)
const filenames = files.keys()
// Store
let storeData = {}
// Check if store/index.js exists
-const indexFilename = filenames.find((filename) => filename.includes('./index.'))
+let indexFilename
+filenames.forEach((filename) => {
+ if (filename.indexOf('./index.') !== -1) {
+ indexFilename = filename
+ }
+})
if (indexFilename) {
storeData = getModule(indexFilename)
}
@@ -40,7 +45,9 @@ if (typeof storeData !== 'function') {
// createStore
export const createStore = storeData instanceof Function ? storeData : () => {
- return new Vuex.Store(Object.assign({}, storeData, {
+ return new Vuex.Store(Object.assign({
+ strict: (process.env.NODE_ENV !== 'production'),
+ }, storeData, {
state: storeData.state instanceof Function ? storeData.state() : {}
}))
}
diff --git a/lib/app/utils.js b/lib/app/utils.js
index edd88defb2..a7b6603de0 100644
--- a/lib/app/utils.js
+++ b/lib/app/utils.js
@@ -1,12 +1,28 @@
-'use strict'
import Vue from 'vue'
const noopData = () => ({})
-export function applyAsyncData (Component, asyncData = {}) {
+// window.onNuxtReady(() => console.log('Ready')) hook
+// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
+if (process.browser) {
+ window._nuxtReadyCbs = []
+ window.onNuxtReady = function (cb) {
+ window._nuxtReadyCbs.push(cb)
+ }
+}
+
+export function applyAsyncData(Component, asyncData) {
const ComponentData = Component.options.data || noopData
+ // Prevent calling this method for each request on SSR context
+ if (!asyncData && Component.options.hasAsyncData) {
+ return
+ }
+ Component.options.hasAsyncData = true
Component.options.data = function () {
const data = ComponentData.call(this)
+ if (this.$ssrContext) {
+ asyncData = this.$ssrContext.asyncData[Component.cid]
+ }
return { ...data, ...asyncData }
}
if (Component._Ctor && Component._Ctor.options) {
@@ -14,7 +30,11 @@ export function applyAsyncData (Component, asyncData = {}) {
}
}
-export function sanitizeComponent (Component) {
+export function sanitizeComponent(Component) {
+ // If Component already sanitized
+ if (Component.options && Component._Ctor === Component) {
+ return Component
+ }
if (!Component.options) {
Component = Vue.extend(Component) // fix issue #6
Component._Ctor = Component
@@ -29,7 +49,7 @@ export function sanitizeComponent (Component) {
return Component
}
-export function getMatchedComponents (route) {
+export function getMatchedComponents(route) {
return [].concat.apply([], route.matched.map(function (m) {
return Object.keys(m.components).map(function (key) {
return m.components[key]
@@ -37,7 +57,7 @@ export function getMatchedComponents (route) {
}))
}
-export function getMatchedComponentsInstances (route) {
+export function getMatchedComponentsInstances(route) {
return [].concat.apply([], route.matched.map(function (m) {
return Object.keys(m.instances).map(function (key) {
return m.instances[key]
@@ -45,7 +65,7 @@ export function getMatchedComponentsInstances (route) {
}))
}
-export function flatMapComponents (route, fn) {
+export function flatMapComponents(route, fn) {
return Array.prototype.concat.apply([], route.matched.map(function (m, index) {
return Object.keys(m.components).map(function (key) {
return fn(m.components[key], m.instances[key], m, key, index)
@@ -53,54 +73,96 @@ export function flatMapComponents (route, fn) {
}))
}
-export function getContext (context, app) {
- let ctx = {
- isServer: !!context.isServer,
- isClient: !!context.isClient,
- isDev: <%= isDev %>,
- app: app,
- <%= (store ? 'store: context.store,' : '') %>
- route: (context.to ? context.to : context.route),
- payload: context.payload,
- error: context.error,
- base: '<%= router.base %>',
- env: <%= JSON.stringify(env) %>,
- hotReload: context.hotReload || false
- }
- const next = context.next
- ctx.params = ctx.route.params || {}
- ctx.query = ctx.route.query || {}
- ctx.redirect = function (status, path, query) {
- if (!status) return
- ctx._redirected = true // Used in middleware
- // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' })
- if (typeof status === 'string' && (typeof path === 'undefined' || typeof path === 'object')) {
- query = path || {}
- path = status
- status = 302
- }
- next({
- path: path,
- query: query,
- status: status
+export async function resolveRouteComponents(route) {
+ return await Promise.all(
+ flatMapComponents(route, async (Component, _, match, key) => {
+ // If component is a function, resolve it
+ if (typeof Component === 'function' && !Component.options) {
+ Component = await Component()
+ }
+ return match.components[key] = sanitizeComponent(Component)
})
- }
- if (context.req) ctx.req = context.req
- if (context.res) ctx.res = context.res
- return ctx
+ )
}
-export function middlewareSeries (promises, context) {
- if (!promises.length || context._redirected) {
+async function getRouteData(route) {
+ // Make sure the components are resolved (code-splitting)
+ await resolveRouteComponents(route)
+ // Send back a copy of route with meta based on Component definition
+ return {
+ ...route,
+ meta: getMatchedComponents(route).map((Component) => {
+ return Component.options.meta || {}
+ })
+ }
+}
+
+export async function setContext(app, context) {
+ const route = (context.to ? context.to : context.route)
+ // If context not defined, create it
+ if (!app.context) {
+ app.context = {
+ get isServer() {
+ console.warn('context.isServer has been deprecated, please use process.server instead.')
+ return process.server
+ },
+ get isClient() {
+ console.warn('context.isClient has been deprecated, please use process.client instead.')
+ return process.client
+ },
+ isStatic: process.static,
+ isDev: <%= isDev %>,
+ isHMR: false,
+ app,
+ <%= (store ? 'store: app.store,' : '') %>
+ payload: context.payload,
+ error: context.error,
+ base: '<%= router.base %>',
+ env: <%= JSON.stringify(env) %>
+ }
+ // Only set once
+ if (context.req) app.context.req = context.req
+ if (context.res) app.context.res = context.res
+ app.context.redirect = function (status, path, query) {
+ if (!status) return
+ app.context._redirected = true // Used in middleware
+ // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' })
+ if (typeof status === 'string' && (typeof path === 'undefined' || typeof path === 'object')) {
+ query = path || {}
+ path = status
+ status = 302
+ }
+ app.context.next({
+ path: path,
+ query: query,
+ status: status
+ })
+ }
+ if (process.server) app.context.beforeNuxtRender = (fn) => context.beforeRenderFns.push(fn)
+ if (process.client) app.context.nuxtState = window.__NUXT__
+ }
+ // Dynamic keys
+ app.context.next = context.next
+ app.context._redirected = false
+ app.context._errored = false
+ app.context.isHMR = !!context.isHMR
+ if (context.route) app.context.route = await getRouteData(context.route)
+ app.context.params = app.context.route.params || {}
+ app.context.query = app.context.route.query || {}
+ if (context.from) app.context.from = await getRouteData(context.from)
+}
+
+export function middlewareSeries(promises, appContext) {
+ if (!promises.length || appContext._redirected || appContext._errored) {
return Promise.resolve()
}
- return promisify(promises[0], context)
+ return promisify(promises[0], appContext)
.then(() => {
- return middlewareSeries(promises.slice(1), context)
+ return middlewareSeries(promises.slice(1), appContext)
})
}
-export function promisify (fn, context) {
+export function promisify(fn, context) {
let promise
if (fn.length === 2) {
// fn(context, callback)
@@ -123,15 +185,18 @@ export function promisify (fn, context) {
}
// Imported from vue-router
-export function getLocation (base) {
+export function getLocation(base, mode) {
var path = window.location.pathname
+ if (mode === 'hash') {
+ return window.location.hash.replace(/^#\//, '')
+ }
if (base && path.indexOf(base) === 0) {
path = path.slice(base.length)
}
return (path || '/') + window.location.search + window.location.hash
}
-export function urlJoin () {
+export function urlJoin() {
return [].slice.call(arguments).join('/').replace(/\/+/g, '/')
}
@@ -144,10 +209,21 @@ export function urlJoin () {
* @param {Object=} options
* @return {!function(Object=, Object=)}
*/
-export function compile (str, options) {
+export function compile(str, options) {
return tokensToFunction(parse(str, options))
}
+export function getQueryDiff(toQuery, fromQuery) {
+ const diff = {}
+ const queries = { ...toQuery, ...fromQuery }
+ for (const k in queries) {
+ if (String(toQuery[k]) !== String(fromQuery[k])) {
+ diff[k] = true
+ }
+ }
+ return diff
+}
+
/**
* The main path matching regexp utility.
*
@@ -173,7 +249,7 @@ const PATH_REGEXP = new RegExp([
* @param {Object=} options
* @return {!Array}
*/
-function parse (str, options) {
+function parse(str, options) {
var tokens = []
var key = 0
var index = 0
@@ -245,7 +321,7 @@ function parse (str, options) {
* @param {string}
* @return {string}
*/
-function encodeURIComponentPretty (str) {
+function encodeURIComponentPretty(str) {
return encodeURI(str).replace(/[\/?#]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
})
@@ -257,7 +333,7 @@ function encodeURIComponentPretty (str) {
* @param {string}
* @return {string}
*/
-function encodeAsterisk (str) {
+function encodeAsterisk(str) {
return encodeURI(str).replace(/[?#]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
})
@@ -266,7 +342,7 @@ function encodeAsterisk (str) {
/**
* Expose a method for transforming tokens into the path function.
*/
-function tokensToFunction (tokens) {
+function tokensToFunction(tokens) {
// Compile all the tokens into regexps.
var matches = new Array(tokens.length)
@@ -277,7 +353,7 @@ function tokensToFunction (tokens) {
}
}
- return function (obj, opts) {
+ return function(obj, opts) {
var path = ''
var data = obj || {}
var options = opts || {}
@@ -353,7 +429,7 @@ function tokensToFunction (tokens) {
* @param {string} str
* @return {string}
*/
-function escapeString (str) {
+function escapeString(str) {
return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
}
@@ -363,6 +439,6 @@ function escapeString (str) {
* @param {string} group
* @return {string}
*/
-function escapeGroup (group) {
+function escapeGroup(group) {
return group.replace(/([=!:$\/()])/g, '\\$1')
}
diff --git a/lib/views/app.template.html b/lib/app/views/app.template.html
similarity index 100%
rename from lib/views/app.template.html
rename to lib/app/views/app.template.html
diff --git a/lib/app/views/error.html b/lib/app/views/error.html
new file mode 100644
index 0000000000..d769cf4300
--- /dev/null
+++ b/lib/app/views/error.html
@@ -0,0 +1,23 @@
+
+
+
+<%= messages.server_error %>
+
+
+
+
+
+
+
+
+
<%= messages.server_error %>
+
<% if (debug) { %>{{ message }}<% } else { %><%= messages.server_error_details %><% } %>
+
+
+
+
+
diff --git a/lib/app/views/loading/chasing-dots.html b/lib/app/views/loading/chasing-dots.html
new file mode 100644
index 0000000000..7b9db40240
--- /dev/null
+++ b/lib/app/views/loading/chasing-dots.html
@@ -0,0 +1,68 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/circle.html b/lib/app/views/loading/circle.html
new file mode 100644
index 0000000000..d03338274e
--- /dev/null
+++ b/lib/app/views/loading/circle.html
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/app/views/loading/cube-grid.html b/lib/app/views/loading/cube-grid.html
new file mode 100644
index 0000000000..b8c4d754b8
--- /dev/null
+++ b/lib/app/views/loading/cube-grid.html
@@ -0,0 +1,88 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/fading-circle.html b/lib/app/views/loading/fading-circle.html
new file mode 100644
index 0000000000..714186eee1
--- /dev/null
+++ b/lib/app/views/loading/fading-circle.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/app/views/loading/folding-cube.html b/lib/app/views/loading/folding-cube.html
new file mode 100644
index 0000000000..ad54896702
--- /dev/null
+++ b/lib/app/views/loading/folding-cube.html
@@ -0,0 +1,109 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/nuxt.html b/lib/app/views/loading/nuxt.html
new file mode 100644
index 0000000000..57a668686e
--- /dev/null
+++ b/lib/app/views/loading/nuxt.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+
diff --git a/lib/app/views/loading/pulse.html b/lib/app/views/loading/pulse.html
new file mode 100644
index 0000000000..c5fa8ff4f0
--- /dev/null
+++ b/lib/app/views/loading/pulse.html
@@ -0,0 +1,49 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/rectangle-bounce.html b/lib/app/views/loading/rectangle-bounce.html
new file mode 100644
index 0000000000..363b852743
--- /dev/null
+++ b/lib/app/views/loading/rectangle-bounce.html
@@ -0,0 +1,75 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/rotating-plane.html b/lib/app/views/loading/rotating-plane.html
new file mode 100644
index 0000000000..6d409a9297
--- /dev/null
+++ b/lib/app/views/loading/rotating-plane.html
@@ -0,0 +1,45 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/three-bounce.html b/lib/app/views/loading/three-bounce.html
new file mode 100644
index 0000000000..cf24ac71c2
--- /dev/null
+++ b/lib/app/views/loading/three-bounce.html
@@ -0,0 +1,62 @@
+
+
+
+
+
diff --git a/lib/app/views/loading/wandering-cubes.html b/lib/app/views/loading/wandering-cubes.html
new file mode 100644
index 0000000000..c3177a399f
--- /dev/null
+++ b/lib/app/views/loading/wandering-cubes.html
@@ -0,0 +1,69 @@
+
+
+
+
+
diff --git a/lib/build.js b/lib/build.js
deleted file mode 100644
index 968feee155..0000000000
--- a/lib/build.js
+++ /dev/null
@@ -1,565 +0,0 @@
-'use strict'
-
-import _ from 'lodash'
-import chokidar from 'chokidar'
-import fs from 'fs-extra'
-import hash from 'hash-sum'
-import pify from 'pify'
-import webpack from 'webpack'
-import PostCompilePlugin from 'post-compile-webpack-plugin'
-import serialize from 'serialize-javascript'
-import { createBundleRenderer } from 'vue-server-renderer'
-import { join, resolve, basename, dirname } from 'path'
-import { isUrl, r, wp } from './utils'
-import clientWebpackConfig from './webpack/client.config.js'
-import serverWebpackConfig from './webpack/server.config.js'
-const debug = require('debug')('nuxt:build')
-const remove = pify(fs.remove)
-const readFile = pify(fs.readFile)
-const utimes = pify(fs.utimes)
-const writeFile = pify(fs.writeFile)
-const mkdirp = pify(fs.mkdirp)
-const glob = pify(require('glob'))
-
-let webpackStats = 'none'
-debug.color = 2 // force green color
-
-const defaults = {
- analyze: false,
- extractCSS: false,
- publicPath: '/_nuxt/',
- filenames: {
- css: 'common.[chunkhash].css',
- manifest: 'manifest.[hash].js',
- vendor: 'vendor.bundle.[chunkhash].js',
- app: 'nuxt.bundle.[chunkhash].js'
- },
- vendor: [],
- loaders: [],
- plugins: [],
- babel: {},
- postcss: [],
- templates: [],
- watch: []
-}
-const defaultsLoaders = [
- {
- test: /\.(png|jpe?g|gif|svg)$/,
- loader: 'url-loader',
- query: {
- limit: 1000, // 1KO
- name: 'img/[name].[hash:7].[ext]'
- }
- },
- {
- test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
- loader: 'url-loader',
- query: {
- limit: 1000, // 1 KO
- name: 'fonts/[name].[hash:7].[ext]'
- }
- }
-]
-const defaultsPostcss = [
- require('autoprefixer')({
- browsers: ['last 3 versions']
- })
-]
-
-export function options () {
- // Defaults build options
- let extraDefaults = {}
- if (this.options.build && !Array.isArray(this.options.build.loaders)) extraDefaults.loaders = defaultsLoaders
- if (this.options.build && !Array.isArray(this.options.build.postcss)) extraDefaults.postcss = defaultsPostcss
- this.options.build = _.defaultsDeep(this.options.build, defaults, extraDefaults)
- /* istanbul ignore if */
- if (this.dev && isUrl(this.options.build.publicPath)) {
- this.options.build.publicPath = defaults.publicPath
- }
-}
-
-export function production () {
- // Production, create server-renderer
- webpackStats = {
- chunks: false,
- children: false,
- modules: false,
- colors: true
- }
- const serverConfig = getWebpackServerConfig.call(this)
- const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
- const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
- if (fs.existsSync(bundlePath) && fs.existsSync(manifestPath)) {
- const bundle = fs.readFileSync(bundlePath, 'utf8')
- const manifest = fs.readFileSync(manifestPath, 'utf8')
- createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
- addAppTemplate.call(this)
- }
-}
-
-export async function build () {
- // Avoid calling this method multiple times
- if (this._buildDone) {
- return this
- }
- // If building
- if (this._building) {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(this.build())
- }, 300)
- })
- }
- this._building = true
- // Wait for Nuxt.js to be ready
- await this.ready()
- // Check if pages dir exists and warn if not
- this._nuxtPages = typeof this.createRoutes !== 'function'
- if (this._nuxtPages) {
- if (!fs.existsSync(join(this.srcDir, 'pages'))) {
- if (fs.existsSync(join(this.srcDir, '..', 'pages'))) {
- console.error('> No `pages` directory found. Did you mean to run `nuxt` in the parent (`../`) directory?') // eslint-disable-line no-console
- } else {
- console.error('> Couldn\'t find a `pages` directory. Please create one under the project root') // eslint-disable-line no-console
- }
- process.exit(1)
- }
- }
- debug(`App root: ${this.srcDir}`)
- debug(`Generating ${this.buildDir} files...`)
- // Create .nuxt/, .nuxt/components and .nuxt/dist folders
- await remove(r(this.buildDir))
- await mkdirp(r(this.buildDir, 'components'))
- if (!this.dev) {
- await mkdirp(r(this.buildDir, 'dist'))
- }
- // Generate routes and interpret the template files
- await generateRoutesAndFiles.call(this)
- // Generate .nuxt/dist/ files
- await buildFiles.call(this)
- // Flag to set that building is done
- this._buildDone = true
- return this
-}
-
-async function buildFiles () {
- if (this.dev) {
- debug('Adding webpack middleware...')
- createWebpackMiddleware.call(this)
- webpackWatchAndUpdate.call(this)
- watchFiles.call(this)
- } else {
- debug('Building files...')
- await webpackRunClient.call(this)
- await webpackRunServer.call(this)
- addAppTemplate.call(this)
- }
-}
-
-function addAppTemplate () {
- let templatePath = resolve(this.buildDir, 'dist', 'index.html')
- if (fs.existsSync(templatePath)) {
- this.appTemplate = _.template(fs.readFileSync(templatePath, 'utf8'), {
- interpolate: /{{([\s\S]+?)}}/g
- })
- }
-}
-
-async function generateRoutesAndFiles () {
- debug('Generating files...')
- // -- Templates --
- let templatesFiles = [
- 'App.vue',
- 'client.js',
- 'index.js',
- 'middleware.js',
- 'router.js',
- 'server.js',
- 'utils.js',
- 'components/nuxt-error.vue',
- 'components/nuxt-loading.vue',
- 'components/nuxt-child.js',
- 'components/nuxt-link.js',
- 'components/nuxt.vue'
- ]
- const templateVars = {
- options: this.options,
- uniqBy: _.uniqBy,
- isDev: this.dev,
- router: {
- mode: this.options.router.mode,
- base: this.options.router.base,
- middleware: this.options.router.middleware,
- linkActiveClass: this.options.router.linkActiveClass,
- linkExactActiveClass: this.options.router.linkExactActiveClass,
- scrollBehavior: this.options.router.scrollBehavior
- },
- env: this.options.env,
- head: this.options.head,
- middleware: fs.existsSync(join(this.srcDir, 'middleware')),
- store: this.options.store || fs.existsSync(join(this.srcDir, 'store')),
- css: this.options.css,
- plugins: this.options.plugins.map((p, i) => {
- if (typeof p === 'string') p = { src: p }
- p.src = r(this.srcDir, p.src)
- return { src: p.src, ssr: (p.ssr !== false), name: `plugin${i}` }
- }),
- appPath: './App.vue',
- layouts: Object.assign({}, this.options.layouts),
- loading: (typeof this.options.loading === 'string' ? r(this.srcDir, this.options.loading) : this.options.loading),
- transition: this.options.transition,
- components: {
- ErrorPage: this.options.ErrorPage ? r(this.options.ErrorPage) : null
- }
- }
-
- // -- Layouts --
- if (fs.existsSync(resolve(this.srcDir, 'layouts'))) {
- const layoutsFiles = await glob('layouts/*.vue', {cwd: this.srcDir})
- layoutsFiles.forEach((file) => {
- let name = file.split('/').slice(-1)[0].replace('.vue', '')
- if (name === 'error') return
- templateVars.layouts[name] = r(this.srcDir, file)
- })
- if (layoutsFiles.includes('layouts/error.vue')) {
- templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue')
- }
- }
- // If no default layout, create its folder and add the default folder
- if (!templateVars.layouts.default) {
- await mkdirp(r(this.buildDir, 'layouts'))
- templatesFiles.push('layouts/default.vue')
- templateVars.layouts.default = r(__dirname, 'app', 'layouts', 'default.vue')
- }
-
- // -- Routes --
- debug('Generating routes...')
- // If user defined a custom method to create routes
- if (this._nuxtPages) {
- // Use nuxt.js createRoutes bases on pages/
- const files = await glob('pages/**/*.vue', {cwd: this.srcDir})
- templateVars.router.routes = createRoutes(files, this.srcDir)
- } else {
- templateVars.router.routes = this.createRoutes(this.srcDir)
- }
- // router.extendRoutes method
- if (typeof this.options.router.extendRoutes === 'function') {
- // let the user extend the routes
- this.options.router.extendRoutes.call(this, templateVars.router.routes || [], r)
- }
- // Routes for generate command
- this.routes = flatRoutes(templateVars.router.routes || [])
-
- // -- Store --
- // Add store if needed
- if (this.options.store) {
- templatesFiles.push('store.js')
- }
-
- // Resolve template files
- const customTemplateFiles = this.options.build.templates.map(t => t.dst || basename(t.src || t))
- templatesFiles = templatesFiles.map(file => {
- // Skip if custom file was already provided in build.templates[]
- if (customTemplateFiles.indexOf(file) !== -1) {
- return
- }
- // Allow override templates using a file with same name in ${srcDir}/app
- const customPath = r(this.srcDir, 'app', file)
- const customFileExists = fs.existsSync(customPath)
- return {
- src: customFileExists ? customPath : r(__dirname, 'app', file),
- dst: file,
- custom: customFileExists
- }
- }).filter(i => !!i)
-
- // -- Custom templates --
- // Add custom template files
- templatesFiles = templatesFiles.concat(this.options.build.templates.map(t => {
- return Object.assign({
- src: r(this.dir, t.src || t),
- dst: t.dst || basename(t.src || t),
- custom: true
- }, t)
- }))
-
- // Interpret and move template files to .nuxt/
- return Promise.all(templatesFiles.map(async ({ src, dst, options, custom }) => {
- // Add template to watchers
- this.options.build.watch.push(src)
- // Render template to dst
- const fileContent = await readFile(src, 'utf8')
- const template = _.template(fileContent, {
- imports: {
- serialize,
- hash,
- r,
- wp
- }
- })
- const content = template(Object.assign({}, templateVars, {
- options: options || {},
- custom,
- src,
- dst
- }))
- const path = r(this.buildDir, dst)
- // Ensure parent dir exits
- await mkdirp(dirname(path))
- // Write file
- await writeFile(path, content, 'utf8')
- // Fix webpack loop (https://github.com/webpack/watchpack/issues/25#issuecomment-287789288)
- const dateFS = Date.now() / 1000 - 30
- return utimes(path, dateFS, dateFS)
- }))
-}
-
-function createRoutes (files, srcDir) {
- let routes = []
- files.forEach((file) => {
- let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
- let route = { name: '', path: '', component: r(srcDir, file) }
- let parent = routes
- keys.forEach((key, i) => {
- route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '')
- route.name += (key === '_') ? 'all' : ''
- let child = _.find(parent, { name: route.name })
- if (child) {
- if (!child.children) {
- child.children = []
- }
- parent = child.children
- route.path = ''
- } else {
- if (key === 'index' && (i + 1) === keys.length) {
- route.path += (i > 0 ? '' : '/')
- } else {
- route.path += '/' + (key === '_' ? '*' : key.replace('_', ':'))
- if (key !== '_' && key.indexOf('_') !== -1) {
- route.path += '?'
- }
- }
- }
- })
- // Order Routes path
- parent.push(route)
- parent.sort((a, b) => {
- if (!a.path.length || a.path === '/') { return -1 }
- if (!b.path.length || b.path === '/') { return 1 }
- var res = 0
- var _a = a.path.split('/')
- var _b = b.path.split('/')
- for (var i = 0; i < _a.length; i++) {
- if (res !== 0) { break }
- var y = (_a[i].indexOf('*') > -1) ? 2 : (_a[i].indexOf(':') > -1 ? 1 : 0)
- var z = (_b[i].indexOf('*') > -1) ? 2 : (_b[i].indexOf(':') > -1 ? 1 : 0)
- res = y - z
- if (i === _b.length - 1 && res === 0) {
- res = 1
- }
- }
- return res === 0 ? -1 : res
- })
- })
- return cleanChildrenRoutes(routes)
-}
-
-function cleanChildrenRoutes (routes, isChild = false) {
- let start = -1
- let routesIndex = []
- routes.forEach((route) => {
- if (/-index$/.test(route.name) || route.name === 'index') {
- // Save indexOf 'index' key in name
- let res = route.name.split('-')
- let s = res.indexOf('index')
- start = (start === -1 || s < start) ? s : start
- routesIndex.push(res)
- }
- })
- routes.forEach((route) => {
- route.path = (isChild) ? route.path.replace('/', '') : route.path
- if (route.path.indexOf('?') > -1) {
- let names = route.name.split('-')
- let paths = route.path.split('/')
- if (!isChild) { paths.shift() } // clean first / for parents
- routesIndex.forEach((r) => {
- let i = r.indexOf('index') - start // children names
- if (i < paths.length) {
- for (var a = 0; a <= i; a++) {
- if (a === i) { paths[a] = paths[a].replace('?', '') }
- if (a < i && names[a] !== r[a]) { break }
- }
- }
- })
- route.path = (isChild ? '' : '/') + paths.join('/')
- }
- route.name = route.name.replace(/-index$/, '')
- if (route.children) {
- if (route.children.find((child) => child.path === '')) {
- delete route.name
- }
- route.children = cleanChildrenRoutes(route.children, true)
- }
- })
- return routes
-}
-
-function flatRoutes (router, path = '', routes = []) {
- router.forEach((r) => {
- if (!r.path.includes(':') && !r.path.includes('*')) {
- if (r.children) {
- flatRoutes(r.children, path + r.path + '/', routes)
- } else {
- routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
- }
- }
- })
- return routes
-}
-
-function getWebpackClientConfig () {
- return clientWebpackConfig.call(this)
-}
-
-function getWebpackServerConfig () {
- return serverWebpackConfig.call(this)
-}
-
-function createWebpackMiddleware () {
- const clientConfig = getWebpackClientConfig.call(this)
- const host = process.env.HOST || process.env.npm_package_config_nuxt_host || '127.0.0.1'
- const port = process.env.PORT || process.env.npm_package_config_nuxt_port || '3000'
- // setup on the fly compilation + hot-reload
- clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
- clientConfig.plugins.push(
- new webpack.HotModuleReplacementPlugin(),
- new webpack.NoEmitOnErrorsPlugin(),
- new PostCompilePlugin(stats => {
- if (!stats.hasErrors() && !stats.hasWarnings()) {
- console.log(`> Open http://${host}:${port}\n`) // eslint-disable-line no-console
- }
- })
- )
- const clientCompiler = webpack(clientConfig)
- this.clientCompiler = clientCompiler
- // Add the middleware to the instance context
- this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, {
- publicPath: clientConfig.output.publicPath,
- stats: webpackStats,
- quiet: true,
- noInfo: true,
- watchOptions: this.options.watchers.webpack
- }))
- this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
- log: () => {}
- }))
- clientCompiler.plugin('done', () => {
- const fs = this.webpackDevMiddleware.fileSystem
- const filePath = join(clientConfig.output.path, 'index.html')
- if (fs.existsSync(filePath)) {
- const template = fs.readFileSync(filePath, 'utf-8')
- this.appTemplate = _.template(template, {
- interpolate: /{{([\s\S]+?)}}/g
- })
- }
- this.watchHandler()
- })
-}
-
-function webpackWatchAndUpdate () {
- const MFS = require('memory-fs') // <- dependencies of webpack
- const serverFS = new MFS()
- const clientFS = this.clientCompiler.outputFileSystem
- const serverConfig = getWebpackServerConfig.call(this)
- const serverCompiler = webpack(serverConfig)
- const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
- const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
- serverCompiler.outputFileSystem = serverFS
- const watchHandler = (err) => {
- if (err) throw err
- const bundleExists = serverFS.existsSync(bundlePath)
- const manifestExists = clientFS.existsSync(manifestPath)
- if (bundleExists && manifestExists) {
- const bundle = serverFS.readFileSync(bundlePath, 'utf8')
- const manifest = clientFS.readFileSync(manifestPath, 'utf8')
- createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
- }
- }
- this.watchHandler = watchHandler
- this.webpackServerWatcher = serverCompiler.watch(this.options.watchers.webpack, watchHandler)
-}
-
-function webpackRunClient () {
- return new Promise((resolve, reject) => {
- const clientConfig = getWebpackClientConfig.call(this)
- const clientCompiler = webpack(clientConfig)
- clientCompiler.run((err, stats) => {
- if (err) return reject(err)
- console.log('[nuxt:build:client]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
- if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
- resolve()
- })
- })
-}
-
-function webpackRunServer () {
- return new Promise((resolve, reject) => {
- const serverConfig = getWebpackServerConfig.call(this)
- const serverCompiler = webpack(serverConfig)
- serverCompiler.run((err, stats) => {
- if (err) return reject(err)
- console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
- if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
- const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
- const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
- readFile(bundlePath, 'utf8')
- .then(bundle => {
- readFile(manifestPath, 'utf8')
- .then(manifest => {
- createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
- resolve()
- })
- })
- })
- })
-}
-
-function createRenderer (bundle, manifest) {
- // Create bundle renderer to give a fresh context for every request
- this.renderer = createBundleRenderer(bundle, Object.assign({
- clientManifest: manifest,
- runInNewContext: false,
- basedir: this.dir
- }, this.options.build.ssr))
- this.renderToString = pify(this.renderer.renderToString)
- this.renderToStream = this.renderer.renderToStream
-}
-
-function watchFiles () {
- const patterns = [
- r(this.srcDir, 'layouts'),
- r(this.srcDir, 'store'),
- r(this.srcDir, 'middleware'),
- r(this.srcDir, 'layouts/*.vue'),
- r(this.srcDir, 'layouts/**/*.vue')
- ]
- if (this._nuxtPages) {
- patterns.push(r(this.srcDir, 'pages'))
- patterns.push(r(this.srcDir, 'pages/*.vue'))
- patterns.push(r(this.srcDir, 'pages/**/*.vue'))
- }
- const options = Object.assign({}, this.options.watchers.chokidar, {
- ignoreInitial: true
- })
- /* istanbul ignore next */
- const refreshFiles = _.debounce(async () => {
- await generateRoutesAndFiles.call(this)
- }, 200)
- // Watch for internals
- this.filesWatcher = chokidar.watch(patterns, options)
- .on('add', refreshFiles)
- .on('unlink', refreshFiles)
- // Watch for custom provided files
- this.customFilesWatcher = chokidar.watch(_.uniq(this.options.build.watch), options)
- .on('change', refreshFiles)
-}
diff --git a/lib/builder/builder.js b/lib/builder/builder.js
new file mode 100644
index 0000000000..a63e46950c
--- /dev/null
+++ b/lib/builder/builder.js
@@ -0,0 +1,549 @@
+import _ from 'lodash'
+import chokidar from 'chokidar'
+import fs, { remove, readFile, writeFile, mkdirp, existsSync } from 'fs-extra'
+import hash from 'hash-sum'
+import pify from 'pify'
+import webpack from 'webpack'
+import serialize from 'serialize-javascript'
+import { join, resolve, basename, extname, dirname } from 'path'
+import MFS from 'memory-fs'
+import webpackDevMiddleware from 'webpack-dev-middleware'
+import webpackHotMiddleware from 'webpack-hot-middleware'
+import { r, wp, wChunk, createRoutes, sequence, relativeTo, isPureObject } from 'utils'
+import Debug from 'debug'
+import Glob from 'glob'
+import clientWebpackConfig from './webpack/client.config.js'
+import serverWebpackConfig from './webpack/server.config.js'
+import dllWebpackConfig from './webpack/dll.config.js'
+import vueLoaderConfig from './webpack/vue-loader.config'
+import styleLoader from './webpack/style-loader'
+
+const debug = Debug('nuxt:build')
+debug.color = 2 // Force green color
+
+const glob = pify(Glob)
+
+export default class Builder {
+ constructor(nuxt) {
+ this.nuxt = nuxt
+ this.isStatic = false // Flag to know if the build is for a generated app
+ this.options = nuxt.options
+
+ // Fields that set on build
+ this.compilers = []
+ this.webpackDevMiddleware = null
+ this.webpackHotMiddleware = null
+
+ // Mute stats on dev
+ this.webpackStats = this.options.dev ? false : {
+ chunks: false,
+ children: false,
+ modules: false,
+ colors: true,
+ excludeAssets: [
+ /.map$/,
+ /index\..+\.html$/,
+ /vue-ssr-client-manifest.json/
+ ]
+ }
+
+ // Helper to resolve build paths
+ this.relativeToBuild = (...args) => relativeTo(this.options.buildDir, ...args)
+
+ // Bind styleLoader and vueLoader
+ this.styleLoader = styleLoader.bind(this)
+ this.vueLoader = vueLoaderConfig.bind(this)
+
+ this._buildStatus = STATUS.INITIAL
+ }
+
+ get plugins() {
+ return this.options.plugins.map((p, i) => {
+ if (typeof p === 'string') p = { src: p }
+ return {
+ src: this.nuxt.resolvePath(p.src),
+ ssr: (p.ssr !== false),
+ name: basename(p.src, extname(p.src)).replace(/[^a-zA-Z?\d\s:]/g, '') + '_plugin_' + hash(p.src)
+ }
+ })
+ }
+
+ vendor() {
+ return [
+ 'vue',
+ 'vue-router',
+ 'vue-meta',
+ this.options.store && 'vuex'
+ ].concat(this.options.build.vendor).filter(v => v)
+ }
+
+ vendorEntries() {
+ // Used for dll
+ const vendor = this.vendor()
+ const vendorEntries = {}
+ vendor.forEach(v => {
+ try {
+ require.resolve(v)
+ vendorEntries[v] = [ v ]
+ } catch (e) {
+ // Ignore
+ }
+ })
+ return vendorEntries
+ }
+
+ forGenerate() {
+ this.isStatic = true
+ }
+
+ async build() {
+ // Avoid calling build() method multiple times when dev:true
+ /* istanbul ignore if */
+ if (this._buildStatus === STATUS.BUILD_DONE && this.options.dev) {
+ return this
+ }
+ // If building
+ /* istanbul ignore if */
+ if (this._buildStatus === STATUS.BUILDING) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(this.build())
+ }, 1000)
+ })
+ }
+ this._buildStatus = STATUS.BUILDING
+
+ // Wait for nuxt ready
+ await this.nuxt.ready()
+
+ // Call before hook
+ await this.nuxt.callHook('build:before', this, this.options.build)
+
+ // Babel options
+ this.babelOptions = _.defaults(this.options.build.babel, {
+ babelrc: false,
+ cacheDirectory: !!this.options.dev
+ })
+ if (!this.babelOptions.babelrc && !this.babelOptions.presets) {
+ this.babelOptions.presets = [
+ [require.resolve('babel-preset-vue-app'), {
+ targets: { ie: 9, uglify: true }
+ }]
+ ]
+ }
+
+ // Map postcss plugins into instances on object mode once
+ if (isPureObject(this.options.build.postcss)) {
+ if (isPureObject(this.options.build.postcss.plugins)) {
+ this.options.build.postcss.plugins = Object.keys(this.options.build.postcss.plugins)
+ .map(p => {
+ const plugin = require(this.nuxt.resolvePath(p))
+ const opts = this.options.build.postcss.plugins[p]
+ if (opts === false) return // Disabled
+ const instance = plugin(opts)
+ return instance
+ }).filter(e => e)
+ }
+ }
+
+ // Check if pages dir exists and warn if not
+ this._nuxtPages = typeof this.options.build.createRoutes !== 'function'
+ if (this._nuxtPages) {
+ if (!fs.existsSync(join(this.options.srcDir, 'pages'))) {
+ let dir = this.options.srcDir
+ if (fs.existsSync(join(this.options.srcDir, '..', 'pages'))) {
+ throw new Error(`No \`pages\` directory found in ${dir}. Did you mean to run \`nuxt\` in the parent (\`../\`) directory?`)
+ } else {
+ throw new Error(`Couldn't find a \`pages\` directory in ${dir}. Please create one under the project root`)
+ }
+ }
+ }
+
+ debug(`App root: ${this.options.srcDir}`)
+ debug(`Generating ${this.options.buildDir} files...`)
+
+ // Create .nuxt/, .nuxt/components and .nuxt/dist folders
+ await remove(r(this.options.buildDir))
+ await mkdirp(r(this.options.buildDir, 'components'))
+ if (!this.options.dev) {
+ await mkdirp(r(this.options.buildDir, 'dist'))
+ }
+
+ // Generate routes and interpret the template files
+ await this.generateRoutesAndFiles()
+
+ // Start webpack build
+ await this.webpackBuild()
+
+ // Flag to set that building is done
+ this._buildStatus = STATUS.BUILD_DONE
+
+ // Call done hook
+ await this.nuxt.callHook('build:done', this)
+
+ return this
+ }
+
+ async generateRoutesAndFiles() {
+ debug('Generating files...')
+ // -- Templates --
+ let templatesFiles = [
+ 'App.js',
+ 'client.js',
+ 'index.js',
+ 'middleware.js',
+ 'router.js',
+ 'server.js',
+ 'utils.js',
+ 'empty.js',
+ 'components/nuxt-error.vue',
+ 'components/nuxt-loading.vue',
+ 'components/nuxt-child.js',
+ 'components/nuxt-link.js',
+ 'components/nuxt.js',
+ 'components/no-ssr.js',
+ 'views/app.template.html',
+ 'views/error.html'
+ ]
+ const templateVars = {
+ options: this.options,
+ messages: this.options.messages,
+ uniqBy: _.uniqBy,
+ isDev: this.options.dev,
+ debug: this.options.debug,
+ mode: this.options.mode,
+ router: this.options.router,
+ env: this.options.env,
+ head: this.options.head,
+ middleware: fs.existsSync(join(this.options.srcDir, 'middleware')),
+ store: this.options.store,
+ css: this.options.css,
+ plugins: this.plugins,
+ appPath: './App.js',
+ layouts: Object.assign({}, this.options.layouts),
+ loading: typeof this.options.loading === 'string' ? this.relativeToBuild(this.options.srcDir, this.options.loading) : this.options.loading,
+ transition: this.options.transition,
+ layoutTransition: this.options.layoutTransition,
+ components: {
+ ErrorPage: this.options.ErrorPage ? this.relativeToBuild(this.options.ErrorPage) : null
+ }
+ }
+
+ // -- Layouts --
+ if (fs.existsSync(resolve(this.options.srcDir, 'layouts'))) {
+ const layoutsFiles = await glob('layouts/**/*.vue', { cwd: this.options.srcDir })
+ let hasErrorLayout = false
+ layoutsFiles.forEach((file) => {
+ let name = file.split('/').slice(1).join('/').replace(/\.vue$/, '')
+ if (name === 'error') {
+ hasErrorLayout = true
+ return
+ }
+ templateVars.layouts[name] = this.relativeToBuild(this.options.srcDir, file)
+ })
+ if (!templateVars.components.ErrorPage && hasErrorLayout) {
+ templateVars.components.ErrorPage = this.relativeToBuild(this.options.srcDir, 'layouts/error.vue')
+ }
+ }
+ // If no default layout, create its folder and add the default folder
+ if (!templateVars.layouts.default) {
+ await mkdirp(r(this.options.buildDir, 'layouts'))
+ templatesFiles.push('layouts/default.vue')
+ templateVars.layouts.default = './layouts/default.vue'
+ }
+
+ // -- Routes --
+ debug('Generating routes...')
+ // If user defined a custom method to create routes
+ if (this._nuxtPages) {
+ // Use nuxt.js createRoutes bases on pages/
+ const files = await glob('pages/**/*.vue', { cwd: this.options.srcDir })
+ templateVars.router.routes = createRoutes(files, this.options.srcDir)
+ } else {
+ templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir)
+ }
+
+ await this.nuxt.callHook('build:extendRoutes', templateVars.router.routes, r)
+
+ // router.extendRoutes method
+ if (typeof this.options.router.extendRoutes === 'function') {
+ // let the user extend the routes
+ const extendedRoutes = this.options.router.extendRoutes(templateVars.router.routes, r)
+ // Only overwrite routes when something is returned for backwards compatibility
+ if (extendedRoutes !== undefined) {
+ templateVars.router.routes = extendedRoutes
+ }
+ }
+
+ // Make routes accessible for other modules and webpack configs
+ this.routes = templateVars.router.routes
+
+ // -- Store --
+ // Add store if needed
+ if (this.options.store) {
+ templatesFiles.push('store.js')
+ }
+
+ // Resolve template files
+ const customTemplateFiles = this.options.build.templates.map(t => t.dst || basename(t.src || t))
+
+ templatesFiles = templatesFiles.map(file => {
+ // Skip if custom file was already provided in build.templates[]
+ if (customTemplateFiles.indexOf(file) !== -1) {
+ return
+ }
+ // Allow override templates using a file with same name in ${srcDir}/app
+ const customPath = r(this.options.srcDir, 'app', file)
+ const customFileExists = fs.existsSync(customPath)
+
+ return {
+ src: customFileExists
+ ? customPath
+ : r(this.options.nuxtAppDir, file),
+ dst: file,
+ custom: customFileExists
+ }
+ }).filter(i => !!i)
+
+ // -- Custom templates --
+ // Add custom template files
+ templatesFiles = templatesFiles.concat(this.options.build.templates.map(t => {
+ return Object.assign({
+ src: r(this.options.srcDir, t.src || t),
+ dst: t.dst || basename(t.src || t),
+ custom: true
+ }, t)
+ }))
+
+ // -- Loading indicator --
+ if (this.options.loadingIndicator.name) {
+ const indicatorPath1 = resolve(this.options.nuxtAppDir, 'views/loading', this.options.loadingIndicator.name + '.html')
+ const indicatorPath2 = this.nuxt.resolvePath(this.options.loadingIndicator.name)
+ const indicatorPath = existsSync(indicatorPath1) ? indicatorPath1 : (existsSync(indicatorPath2) ? indicatorPath2 : null)
+ if (indicatorPath) {
+ templatesFiles.push({
+ src: indicatorPath,
+ dst: 'loading.html',
+ options: this.options.loadingIndicator
+ })
+ } else {
+ // eslint-disable-next-line no-console
+ console.error(`Could not fetch loading indicator: ${this.options.loadingIndicator.name}`)
+ }
+ }
+
+ await this.nuxt.callHook('build:templates', { templatesFiles, templateVars, resolve: r })
+
+ // Interpret and move template files to .nuxt/
+ await Promise.all(templatesFiles.map(async ({ src, dst, options, custom }) => {
+ // Add template to watchers
+ this.options.build.watch.push(src)
+ // Render template to dst
+ const fileContent = await readFile(src, 'utf8')
+ let content
+ try {
+ const template = _.template(fileContent, {
+ imports: {
+ serialize,
+ hash,
+ r,
+ wp,
+ wChunk,
+ resolvePath: this.nuxt.resolvePath.bind(this.nuxt),
+ relativeToBuild: this.relativeToBuild
+ }
+ })
+ content = template(Object.assign({}, templateVars, {
+ options: options || {},
+ custom,
+ src,
+ dst
+ }))
+ } catch (err) {
+ throw new Error(`Could not compile template ${src}: ${err.message}`)
+ }
+ const path = r(this.options.buildDir, dst)
+ // Ensure parent dir exits
+ await mkdirp(dirname(path))
+ // Write file
+ await writeFile(path, content, 'utf8')
+ }))
+ }
+
+ async webpackBuild() {
+ debug('Building files...')
+ const compilersOptions = []
+
+ // Client
+ const clientConfig = clientWebpackConfig.call(this)
+ compilersOptions.push(clientConfig)
+
+ // Server
+ let serverConfig = null
+ if (this.options.build.ssr) {
+ serverConfig = serverWebpackConfig.call(this)
+ compilersOptions.push(serverConfig)
+ }
+
+ // Alias plugins to their real path
+ this.plugins.forEach(p => {
+ const src = this.relativeToBuild(p.src)
+
+ // Client config
+ if (!clientConfig.resolve.alias[p.name]) {
+ clientConfig.resolve.alias[p.name] = src
+ }
+
+ // Server config
+ if (serverConfig && !serverConfig.resolve.alias[p.name]) {
+ // Alias to noop for ssr:false plugins
+ serverConfig.resolve.alias[p.name] = p.ssr ? src : './empty.js'
+ }
+ })
+
+ // Make a dll plugin after compile to make nuxt dev builds faster
+ if (this.options.build.dll && this.options.dev) {
+ compilersOptions.push(dllWebpackConfig.call(this, clientConfig))
+ }
+
+ // Initialize shared FS and Cache
+ const sharedFS = this.options.dev && new MFS()
+ const sharedCache = {}
+
+ // Initialize compilers
+ this.compilers = compilersOptions.map(compilersOption => {
+ const compiler = webpack(compilersOption)
+ // In dev, write files in memory FS (except for DLL)
+ if (sharedFS && !compiler.name.includes('-dll')) {
+ compiler.outputFileSystem = sharedFS
+ }
+ compiler.cache = sharedCache
+ return compiler
+ })
+
+ // Start Builds
+ await sequence(this.compilers, (compiler) => new Promise(async (resolve, reject) => {
+ const name = compiler.options.name
+ await this.nuxt.callHook('build:compile', { name, compiler })
+
+ // Resolve only when compiler emit done event
+ compiler.plugin('done', async (stats) => {
+ await this.nuxt.callHook('build:compiled', { name, compiler, stats })
+ // Reload renderer if available
+ this.nuxt.renderer.loadResources(sharedFS || fs)
+ // Resolve on next tick
+ process.nextTick(resolve)
+ })
+ // --- Dev Build ---
+ if (this.options.dev) {
+ // Client Build, watch is started by dev-middleware
+ if (compiler.options.name === 'client') {
+ return this.webpackDev(compiler)
+ }
+ // DLL build, should run only once
+ if (compiler.options.name.includes('-dll')) {
+ compiler.run((err, stats) => {
+ if (err) return reject(err)
+ debug('[DLL] updated')
+ })
+ return
+ }
+ // Server, build and watch for changes
+ compiler.watch(this.options.watchers.webpack, (err) => {
+ /* istanbul ignore if */
+ if (err) return reject(err)
+ })
+ return
+ }
+ // --- Production Build ---
+ compiler.run((err, stats) => {
+ /* istanbul ignore if */
+ if (err) {
+ console.error(err) // eslint-disable-line no-console
+ return reject(err)
+ }
+
+ // Show build stats for production
+ console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console
+
+ /* istanbul ignore if */
+ if (stats.hasErrors()) {
+ return reject(new Error('Webpack build exited with errors'))
+ }
+ })
+ }))
+ }
+
+ webpackDev(compiler) {
+ debug('Adding webpack middleware...')
+
+ // Create webpack dev middleware
+ this.webpackDevMiddleware = pify(webpackDevMiddleware(compiler, Object.assign({
+ publicPath: this.options.build.publicPath,
+ stats: this.webpackStats,
+ noInfo: true,
+ quiet: true,
+ watchOptions: this.options.watchers.webpack
+ }, this.options.build.devMiddleware)))
+
+ this.webpackHotMiddleware = pify(webpackHotMiddleware(compiler, Object.assign({
+ log: false,
+ heartbeat: 10000
+ }, this.options.build.hotMiddleware)))
+
+ // Inject to renderer instance
+ if (this.nuxt.renderer) {
+ this.nuxt.renderer.webpackDevMiddleware = this.webpackDevMiddleware
+ this.nuxt.renderer.webpackHotMiddleware = this.webpackHotMiddleware
+ }
+
+ // Stop webpack middleware on nuxt.close()
+ this.nuxt.hook('close', () => new Promise(resolve => {
+ this.webpackDevMiddleware.close(() => resolve())
+ }))
+
+ // Start watching files
+ this.watchFiles()
+ }
+
+ watchFiles() {
+ const patterns = [
+ r(this.options.srcDir, 'layouts'),
+ r(this.options.srcDir, 'store'),
+ r(this.options.srcDir, 'middleware'),
+ r(this.options.srcDir, 'layouts/*.vue'),
+ r(this.options.srcDir, 'layouts/**/*.vue')
+ ]
+ if (this._nuxtPages) {
+ patterns.push(r(this.options.srcDir, 'pages'))
+ patterns.push(r(this.options.srcDir, 'pages/*.vue'))
+ patterns.push(r(this.options.srcDir, 'pages/**/*.vue'))
+ }
+ const options = Object.assign({}, this.options.watchers.chokidar, {
+ ignoreInitial: true
+ })
+ /* istanbul ignore next */
+ const refreshFiles = _.debounce(() => this.generateRoutesAndFiles(), 200)
+
+ // Watch for src Files
+ let filesWatcher = chokidar.watch(patterns, options)
+ .on('add', refreshFiles)
+ .on('unlink', refreshFiles)
+
+ // Watch for custom provided files
+ let customFilesWatcher = chokidar.watch(_.uniq(this.options.build.watch), options)
+ .on('change', refreshFiles)
+
+ // Stop watching on nuxt.close()
+ this.nuxt.hook('close', () => {
+ filesWatcher.close()
+ customFilesWatcher.close()
+ })
+ }
+}
+
+const STATUS = {
+ INITIAL: 1,
+ BUILD_DONE: 2,
+ BUILDING: 3
+}
diff --git a/lib/builder/generator.js b/lib/builder/generator.js
new file mode 100644
index 0000000000..e9367ba253
--- /dev/null
+++ b/lib/builder/generator.js
@@ -0,0 +1,206 @@
+import { copy, remove, writeFile, mkdirp, removeSync, existsSync } from 'fs-extra'
+import _ from 'lodash'
+import { resolve, join, dirname, sep } from 'path'
+import { minify } from 'html-minifier'
+import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils'
+import Debug from 'debug'
+
+const debug = Debug('nuxt:generate')
+
+export default class Generator {
+ constructor(nuxt, builder) {
+ this.nuxt = nuxt
+ this.options = nuxt.options
+ this.builder = builder
+
+ // Set variables
+ this.generateRoutes = resolve(this.options.srcDir, 'static')
+ this.srcBuiltPath = resolve(this.options.buildDir, 'dist')
+ this.distPath = resolve(this.options.rootDir, this.options.generate.dir)
+ this.distNuxtPath = join(this.distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath))
+ }
+
+ async generate({ build = true, init = true } = {}) {
+ // Wait for nuxt be ready
+ await this.nuxt.ready()
+
+ // Call before hook
+ await this.nuxt.callHook('generate:before', this, this.options.generate)
+
+ const s = Date.now()
+ let errors = []
+
+ // Add flag to set process.static
+ this.builder.forGenerate()
+
+ // Start build process
+ if (build) {
+ await this.builder.build()
+ }
+
+ // Initialize dist directory
+ if (init) {
+ await this.initDist()
+ }
+
+ // Resolve config.generate.routes promises before generating the routes
+ let generateRoutes = []
+ if (this.options.router.mode !== 'hash') {
+ try {
+ generateRoutes = await promisifyRoute(this.options.generate.routes || [])
+ } catch (e) {
+ console.error('Could not resolve routes') // eslint-disable-line no-console
+ throw e // eslint-disable-line no-unreachable
+ }
+ }
+
+ // Generate only index.html for router.mode = 'hash'
+ let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes)
+ routes = this.decorateWithPayloads(routes, generateRoutes)
+
+ // extendRoutes hook
+ await this.nuxt.callHook('generate:extendRoutes', routes)
+
+ // Start generate process
+ while (routes.length) {
+ let n = 0
+ await Promise.all(routes.splice(0, this.options.generate.concurrency).map(async ({ route, payload }) => {
+ await waitFor(n++ * this.options.generate.interval)
+ await this.generateRoute({ route, payload, errors })
+ await this.nuxt.callHook('generate:routeCreated', route)
+ }))
+ }
+
+ const indexPath = join(this.distPath, 'index.html')
+ if (existsSync(indexPath)) {
+ // Copy /index.html to /200.html for surge SPA
+ // https://surge.sh/help/adding-a-200-page-for-client-side-routing
+ const _200Path = join(this.distPath, '200.html')
+ if (!existsSync(_200Path)) {
+ await copy(indexPath, _200Path)
+ }
+ }
+
+ const duration = Math.round((Date.now() - s) / 100) / 10
+ debug(`HTML Files generated in ${duration}s`)
+
+ // done hook
+ await this.nuxt.callHook('generate:done', this, errors)
+
+ if (errors.length) {
+ const report = errors.map(({ type, route, error }) => {
+ /* istanbul ignore if */
+ if (type === 'unhandled') {
+ return `Route: '${route}'\n${error.stack}`
+ } else {
+ return `Route: '${route}' thrown an error: \n` + JSON.stringify(error)
+ }
+ })
+ console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console
+ }
+
+ return { duration, errors }
+ }
+
+ async initDist() {
+ // Clean destination folder
+ await remove(this.distPath)
+ debug('Destination folder cleaned')
+
+ // Copy static and built files
+ /* istanbul ignore if */
+ if (existsSync(this.generateRoutes)) {
+ await copy(this.generateRoutes, this.distPath)
+ }
+ await copy(this.srcBuiltPath, this.distNuxtPath)
+
+ // Add .nojekyll file to let Github Pages add the _nuxt/ folder
+ // https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/
+ const nojekyllPath = resolve(this.distPath, '.nojekyll')
+ writeFile(nojekyllPath, '')
+
+ // Cleanup SSR related files
+ const extraFiles = [
+ 'index.spa.html',
+ 'index.ssr.html',
+ 'server-bundle.json',
+ 'vue-ssr-client-manifest.json'
+ ].map(file => resolve(this.distNuxtPath, file))
+
+ extraFiles.forEach(file => {
+ if (existsSync(file)) {
+ removeSync(file)
+ }
+ })
+
+ debug('Static & build files copied')
+ }
+
+ decorateWithPayloads(routes, generateRoutes) {
+ let routeMap = {}
+ // Fill routeMap for known routes
+ routes.forEach((route) => {
+ routeMap[route] = {
+ route,
+ payload: null
+ }
+ })
+ // Fill routeMap with given generate.routes
+ generateRoutes.forEach((route) => {
+ // route is either a string or like { route : '/my_route/1', payload: {} }
+ const path = _.isString(route) ? route : route.route
+ routeMap[path] = {
+ route: path,
+ payload: route.payload || null
+ }
+ })
+ return _.values(routeMap)
+ }
+
+ async generateRoute({ route, payload = {}, errors = [] }) {
+ let html
+
+ try {
+ const res = await this.nuxt.renderer.renderRoute(route, { _generate: true, payload })
+ html = res.html
+ if (res.error) {
+ errors.push({ type: 'handled', route, error: res.error })
+ }
+ } catch (err) {
+ /* istanbul ignore next */
+ return errors.push({ type: 'unhandled', route, error: err })
+ }
+
+ if (this.options.generate.minify) {
+ try {
+ html = minify(html, this.options.generate.minify)
+ } catch (err) /* istanbul ignore next */ {
+ const minifyErr = new Error(`HTML minification failed. Make sure the route generates valid HTML. Failed HTML:\n ${html}`)
+ errors.push({ type: 'unhandled', route, error: minifyErr })
+ }
+ }
+
+ let path
+
+ if (this.options.generate.subFolders) {
+ path = join(route, sep, 'index.html') // /about -> /about/index.html
+ path = path === '/404/index.html' ? '/404.html' : path // /404 -> /404.html
+ } else {
+ path =
+ route.length > 1 ? join(sep, route + '.html') : join(sep, 'index.html')
+ }
+
+ // Call hook to let user update the path & html
+ const page = { route, path, html }
+ await this.nuxt.callHook('generate:page', page)
+
+ debug('Generate file: ' + page.path)
+ page.path = join(this.distPath, page.path)
+
+ // Make sure the sub folders are created
+ await mkdirp(dirname(page.path))
+ await writeFile(page.path, page.html, 'utf8')
+
+ return true
+ }
+}
diff --git a/lib/builder/index.js b/lib/builder/index.js
new file mode 100755
index 0000000000..7e52dab70a
--- /dev/null
+++ b/lib/builder/index.js
@@ -0,0 +1,7 @@
+import Builder from './builder'
+import Generator from './generator'
+
+export {
+ Builder,
+ Generator
+}
diff --git a/lib/builder/webpack/base.config.js b/lib/builder/webpack/base.config.js
new file mode 100644
index 0000000000..987e32acb7
--- /dev/null
+++ b/lib/builder/webpack/base.config.js
@@ -0,0 +1,156 @@
+import ExtractTextPlugin from 'extract-text-webpack-plugin'
+import { cloneDeep } from 'lodash'
+import { join, resolve } from 'path'
+import webpack from 'webpack'
+import { isUrl, urlJoin } from 'utils'
+import TimeFixPlugin from './timefix-plugin'
+
+/*
+|--------------------------------------------------------------------------
+| Webpack Shared Config
+|
+| This is the config which is extended by the server and client
+| webpack config files
+|--------------------------------------------------------------------------
+*/
+export default function webpackBaseConfig(name) {
+ const nodeModulesDir = join(__dirname, '..', 'node_modules')
+
+ const config = {
+ name,
+ devtool: this.options.dev ? 'cheap-module-source-map' : 'nosources-source-map',
+ entry: {
+ app: null
+ },
+ output: {
+ path: resolve(this.options.buildDir, 'dist'),
+ filename: this.options.build.filenames.app,
+ chunkFilename: this.options.build.filenames.chunk,
+ publicPath: (isUrl(this.options.build.publicPath)
+ ? this.options.build.publicPath
+ : urlJoin(this.options.router.base, this.options.build.publicPath))
+ },
+ performance: {
+ maxEntrypointSize: 1000000,
+ maxAssetSize: 300000,
+ hints: this.options.dev ? false : 'warning'
+ },
+ resolve: {
+ extensions: ['.js', '.json', '.vue', '.ts'],
+ alias: {
+ '~': join(this.options.srcDir),
+ '~~': join(this.options.rootDir),
+ '@': join(this.options.srcDir),
+ '@@': join(this.options.rootDir),
+ // Used by vue-loader so we can use in templates
+ // with
+ 'assets': join(this.options.srcDir, 'assets'),
+ 'static': join(this.options.srcDir, 'static')
+ },
+ modules: [
+ this.options.modulesDir,
+ nodeModulesDir
+ ]
+ },
+ resolveLoader: {
+ modules: [
+ this.options.modulesDir,
+ nodeModulesDir
+ ]
+ },
+ module: {
+ noParse: /es6-promise\.js$/, // Avoid webpack shimming process
+ rules: [
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader',
+ options: this.vueLoader()
+ },
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ exclude: /node_modules/,
+ options: Object.assign({}, this.babelOptions)
+ },
+ { test: /\.css$/, use: this.styleLoader('css') },
+ { test: /\.less$/, use: this.styleLoader('less', 'less-loader') },
+ { test: /\.sass$/, use: this.styleLoader('sass', {loader: 'sass-loader', options: { indentedSyntax: true }}) },
+ { test: /\.scss$/, use: this.styleLoader('scss', 'sass-loader') },
+ { test: /\.styl(us)?$/, use: this.styleLoader('stylus', 'stylus-loader') },
+ {
+ test: /\.(png|jpe?g|gif|svg)$/,
+ loader: 'url-loader',
+ options: {
+ limit: 1000, // 1KO
+ name: 'img/[name].[hash:7].[ext]'
+ }
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+ loader: 'url-loader',
+ options: {
+ limit: 1000, // 1 KO
+ name: 'fonts/[name].[hash:7].[ext]'
+ }
+ },
+ {
+ test: /\.(webm|mp4)$/,
+ loader: 'file-loader',
+ options: {
+ name: 'videos/[name].[hash:7].[ext]'
+ }
+ }
+ ]
+ },
+ plugins: this.options.build.plugins
+ }
+
+ // Add timefix-plugin before others plugins
+ config.plugins.unshift(new TimeFixPlugin())
+
+ // CSS extraction
+ const extractCSS = this.options.build.extractCSS
+ if (extractCSS) {
+ const extractOptions = Object.assign(
+ { filename: this.options.build.filenames.css },
+ typeof extractCSS === 'object' ? extractCSS : {}
+ )
+ config.plugins.push(new ExtractTextPlugin(extractOptions))
+ }
+
+ // Workaround for hiding Warnings about plugins without a default export (#1179)
+ config.plugins.push({
+ apply(compiler) {
+ compiler.plugin('done', stats => {
+ stats.compilation.warnings = stats.compilation.warnings.filter(warn => {
+ if (warn.name === 'ModuleDependencyWarning' && warn.message.includes(`export 'default'`) && warn.message.includes('plugin')) {
+ return false
+ }
+ return true
+ })
+ })
+ }
+ })
+
+ // --------------------------------------
+ // Dev specific config
+ // --------------------------------------
+ if (this.options.dev) {
+ //
+ }
+
+ // --------------------------------------
+ // Production specific config
+ // --------------------------------------
+ if (!this.options.dev) {
+ // This is needed in webpack 2 for minify CSS
+ config.plugins.push(
+ new webpack.LoaderOptionsPlugin({
+ minimize: true
+ })
+ )
+ }
+
+ // Clone deep avoid leaking config between Client and Server
+ return cloneDeep(config)
+}
diff --git a/lib/builder/webpack/client.config.js b/lib/builder/webpack/client.config.js
new file mode 100644
index 0000000000..47d766417d
--- /dev/null
+++ b/lib/builder/webpack/client.config.js
@@ -0,0 +1,238 @@
+import { each } from 'lodash'
+import webpack from 'webpack'
+import VueSSRClientPlugin from 'vue-server-renderer/client-plugin'
+import HTMLPlugin from 'html-webpack-plugin'
+import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
+import UglifyJSPlugin from 'uglifyjs-webpack-plugin'
+import ProgressBarPlugin from 'progress-bar-webpack-plugin'
+import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
+import { resolve } from 'path'
+import { existsSync } from 'fs'
+import Debug from 'debug'
+import base from './base.config.js'
+
+const debug = Debug('nuxt:build')
+debug.color = 2 // Force green color
+
+/*
+|--------------------------------------------------------------------------
+| Webpack Client Config
+|
+| Generate public/dist/client-vendor-bundle.js
+| Generate public/dist/client-bundle.js
+|
+| In production, will generate public/dist/style.css
+|--------------------------------------------------------------------------
+*/
+export default function webpackClientConfig() {
+ let config = base.call(this, 'client')
+
+ // Entry points
+ config.entry.app = resolve(this.options.buildDir, 'client.js')
+ config.entry.vendor = this.vendor()
+
+ // Add CommonChunks plugin
+ commonChunksPlugin.call(this, config)
+
+ // Env object defined in nuxt.config.js
+ let env = {}
+ each(this.options.env, (value, key) => {
+ env['process.env.' + key] = (['boolean', 'number'].indexOf(typeof value) !== -1 ? value : JSON.stringify(value))
+ })
+
+ // Generate output HTML for SPA
+ config.plugins.push(
+ new HTMLPlugin({
+ filename: 'index.spa.html',
+ template: this.options.appTemplatePath,
+ inject: true,
+ chunksSortMode: 'dependency'
+ })
+ )
+
+ // Generate output HTML for SSR
+ if (this.options.build.ssr) {
+ config.plugins.push(
+ new HTMLPlugin({
+ filename: 'index.ssr.html',
+ template: this.options.appTemplatePath,
+ inject: false // Resources will be injected using bundleRenderer
+ })
+ )
+ }
+
+ // Generate vue-ssr-client-manifest
+ config.plugins.push(
+ new VueSSRClientPlugin({
+ filename: 'vue-ssr-client-manifest.json'
+ })
+ )
+
+ // Extract webpack runtime & manifest
+ config.plugins.push(
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'manifest',
+ minChunks: Infinity,
+ filename: this.options.build.filenames.manifest
+ })
+ )
+
+ // Define Env
+ config.plugins.push(
+ new webpack.DefinePlugin(Object.assign(env, {
+ 'process.env.NODE_ENV': JSON.stringify(env.NODE_ENV || (this.options.dev ? 'development' : 'production')),
+ 'process.env.VUE_ENV': JSON.stringify('client'),
+ 'process.mode': JSON.stringify(this.options.mode),
+ 'process.browser': true,
+ 'process.client': true,
+ 'process.server': false,
+ 'process.static': this.isStatic
+ }))
+ )
+
+ // Build progress bar
+ config.plugins.push(
+ new ProgressBarPlugin()
+ )
+
+ // --------------------------------------
+ // Dev specific config
+ // --------------------------------------
+ if (this.options.dev) {
+ // Add friendly error plugin
+ config.plugins.push(new FriendlyErrorsWebpackPlugin())
+
+ // https://webpack.js.org/plugins/named-modules-plugin
+ config.plugins.push(new webpack.NamedModulesPlugin())
+
+ // Add HMR support
+ config.entry.app = [
+ // https://github.com/glenjamin/webpack-hot-middleware#config
+ `webpack-hot-middleware/client?name=client&reload=true&timeout=30000&path=${this.options.router.base}/__webpack_hmr`.replace(/\/\//g, '/'),
+ config.entry.app
+ ]
+ config.plugins.push(
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoEmitOnErrorsPlugin()
+ )
+
+ // DllReferencePlugin
+ if (this.options.build.dll) {
+ dllPlugin.call(this, config)
+ }
+ }
+
+ // --------------------------------------
+ // Production specific config
+ // --------------------------------------
+ if (!this.options.dev) {
+ // Scope Hoisting
+ if (this.options.build.scopeHoisting === true) {
+ config.plugins.push(new webpack.optimize.ModuleConcatenationPlugin())
+ }
+
+ // https://webpack.js.org/plugins/hashed-module-ids-plugin
+ config.plugins.push(new webpack.HashedModuleIdsPlugin())
+
+ // Minify JS
+ // https://github.com/webpack-contrib/uglifyjs-webpack-plugin
+ config.plugins.push(
+ new UglifyJSPlugin({
+ cache: true,
+ sourceMap: true,
+ extractComments: {
+ filename: 'LICENSES'
+ }
+ })
+ )
+
+ // Webpack Bundle Analyzer
+ if (this.options.build.analyze) {
+ config.plugins.push(new BundleAnalyzerPlugin(Object.assign({}, this.options.build.analyze)))
+ }
+ }
+
+ // Extend config
+ if (typeof this.options.build.extend === 'function') {
+ const isDev = this.options.dev
+ const extendedConfig = this.options.build.extend.call(this, config, {
+ get dev() {
+ console.warn('dev has been deprecated in build.extend(), please use isDev') // eslint-disable-line no-console
+ return isDev
+ },
+ isDev,
+ isClient: true
+ })
+ // Only overwrite config when something is returned for backwards compatibility
+ if (extendedConfig !== undefined) {
+ config = extendedConfig
+ }
+ }
+
+ return config
+}
+
+// --------------------------------------------------------------------------
+// Adds Common Chunks Plugin
+// --------------------------------------------------------------------------
+function commonChunksPlugin(config) {
+ const _this = this
+ const totalPages = _this.routes ? _this.routes.length : 0
+
+ // This well-known vendor may exist as a dependency of other requests.
+ const maybeVendor = [
+ '/core-js/',
+ '/regenerator-runtime/',
+ '/es6-promise/',
+ '/babel-runtime/',
+ '/lodash/'
+ ]
+
+ // Create explicit vendor chunk
+ config.plugins.unshift(
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ filename: this.options.build.filenames.vendor,
+ minChunks(module, count) {
+ // Detect and externalize well-known vendor if detected
+ if (module.context && maybeVendor.some(v => module.context.includes(v))) {
+ return true
+ }
+ // A module is extracted into the vendor chunk when...
+ return (
+ // If it's inside node_modules
+ /node_modules/.test(module.context) &&
+ // Do not externalize if the request is a CSS file
+ !/\.(css|less|scss|sass|styl|stylus)$/.test(module.request) &&
+ // Used in at-least 1/2 of the total pages
+ (totalPages <= 2 ? count >= totalPages : count >= totalPages * 0.5)
+ )
+ }
+ })
+ )
+}
+
+// --------------------------------------------------------------------------
+// Adds DLL plugin
+// https://github.com/webpack/webpack/tree/master/examples/dll-user
+// --------------------------------------------------------------------------
+function dllPlugin(config) {
+ const _dlls = []
+ const vendorEntries = this.vendorEntries()
+ const dllDir = resolve(this.options.cacheDir, config.name + '-dll')
+ Object.keys(vendorEntries).forEach(v => {
+ const dllManifestFile = resolve(dllDir, v + '-manifest.json')
+ if (existsSync(dllManifestFile)) {
+ _dlls.push(v)
+ config.plugins.push(
+ new webpack.DllReferencePlugin({
+ // context: this.options.rootDir,
+ manifest: dllManifestFile // Using full path to allow finding .js dll file
+ })
+ )
+ }
+ })
+ if (_dlls.length) {
+ debug('Using dll for ' + _dlls.join(','))
+ }
+}
diff --git a/lib/builder/webpack/dll.config.js b/lib/builder/webpack/dll.config.js
new file mode 100644
index 0000000000..725015c7df
--- /dev/null
+++ b/lib/builder/webpack/dll.config.js
@@ -0,0 +1,46 @@
+import webpack from 'webpack'
+import { resolve } from 'path'
+import ClientConfig from './client.config'
+
+/*
+|--------------------------------------------------------------------------
+| Webpack Dll Config
+| https://github.com/webpack/webpack/tree/master/examples/dll
+|--------------------------------------------------------------------------
+*/
+export default function webpackDllConfig(_refConfig) {
+ const refConfig = _refConfig || new ClientConfig()
+
+ const name = refConfig.name + '-dll'
+ const dllDir = resolve(this.options.cacheDir, name)
+
+ let config = {
+ name,
+ entry: this.vendorEntries(),
+ // context: this.options.rootDir,
+ resolve: refConfig.resolve,
+ target: refConfig.target,
+ resolveLoader: refConfig.resolveLoader,
+ module: refConfig.module,
+ plugins: []
+ }
+
+ config.output = {
+ path: dllDir,
+ filename: '[name]_[hash].js',
+ library: '[name]_[hash]'
+ }
+
+ config.plugins.push(
+ new webpack.DllPlugin({
+ // The path to the manifest file which maps between
+ // modules included in a bundle and the internal IDs
+ // within that bundle
+ path: resolve(dllDir, '[name]-manifest.json'),
+
+ name: '[name]_[hash]'
+ })
+ )
+
+ return config
+}
diff --git a/lib/builder/webpack/server.config.js b/lib/builder/webpack/server.config.js
new file mode 100644
index 0000000000..47a74cd3cb
--- /dev/null
+++ b/lib/builder/webpack/server.config.js
@@ -0,0 +1,95 @@
+import webpack from 'webpack'
+import VueSSRServerPlugin from 'vue-server-renderer/server-plugin'
+import nodeExternals from 'webpack-node-externals'
+import { each } from 'lodash'
+import { resolve } from 'path'
+import { existsSync } from 'fs'
+import base from './base.config.js'
+
+/*
+|--------------------------------------------------------------------------
+| Webpack Server Config
+|--------------------------------------------------------------------------
+*/
+export default function webpackServerConfig() {
+ let config = base.call(this, 'server')
+
+ // env object defined in nuxt.config.js
+ let env = {}
+ each(this.options.env, (value, key) => {
+ env['process.env.' + key] = (['boolean', 'number'].indexOf(typeof value) !== -1 ? value : JSON.stringify(value))
+ })
+
+ config = Object.assign(config, {
+ target: 'node',
+ node: false,
+ devtool: 'source-map',
+ entry: resolve(this.options.buildDir, 'server.js'),
+ output: Object.assign({}, config.output, {
+ filename: 'server-bundle.js',
+ libraryTarget: 'commonjs2'
+ }),
+ performance: {
+ hints: false,
+ maxAssetSize: Infinity
+ },
+ externals: [],
+ plugins: (config.plugins || []).concat([
+ new VueSSRServerPlugin({
+ filename: 'server-bundle.json'
+ }),
+ new webpack.DefinePlugin(Object.assign(env, {
+ 'process.env.NODE_ENV': JSON.stringify(env.NODE_ENV || (this.options.dev ? 'development' : 'production')),
+ 'process.env.VUE_ENV': JSON.stringify('server'),
+ 'process.mode': JSON.stringify(this.options.mode),
+ 'process.browser': false,
+ 'process.client': false,
+ 'process.server': true,
+ 'process.static': this.isStatic
+ }))
+ ])
+ })
+
+ // https://webpack.js.org/configuration/externals/#externals
+ // https://github.com/liady/webpack-node-externals
+ const moduleDirs = [
+ this.options.modulesDir
+ // Temporary disabled due to vue-server-renderer module search limitations
+ // resolve(__dirname, '..', 'node_modules')
+ ]
+ moduleDirs.forEach(dir => {
+ if (existsSync(dir)) {
+ config.externals.push(nodeExternals({
+ // load non-javascript files with extensions, presumably via loaders
+ whitelist: [/es6-promise|\.(?!(?:js|json)$).{1,5}$/i],
+ modulesDir: dir
+ }))
+ }
+ })
+
+ // --------------------------------------
+ // Production specific config
+ // --------------------------------------
+ if (!this.options.dev) {
+
+ }
+
+ // Extend config
+ if (typeof this.options.build.extend === 'function') {
+ const isDev = this.options.dev
+ const extendedConfig = this.options.build.extend.call(this, config, {
+ get dev() {
+ console.warn('dev has been deprecated in build.extend(), please use isDev') // eslint-disable-line no-console
+ return isDev
+ },
+ isDev,
+ isServer: true
+ })
+ // Only overwrite config when something is returned for backwards compatibility
+ if (extendedConfig !== undefined) {
+ config = extendedConfig
+ }
+ }
+
+ return config
+}
diff --git a/lib/builder/webpack/style-loader.js b/lib/builder/webpack/style-loader.js
new file mode 100755
index 0000000000..1e4b71171d
--- /dev/null
+++ b/lib/builder/webpack/style-loader.js
@@ -0,0 +1,65 @@
+import ExtractTextPlugin from 'extract-text-webpack-plugin'
+import { join } from 'path'
+
+export default function styleLoader(ext, loaders = [], isVueLoader = false) {
+ // Normalize loaders
+ loaders = (Array.isArray(loaders) ? loaders : [loaders]).map(loader => {
+ if (typeof loader === 'string') {
+ loader = { loader }
+ }
+ return Object.assign({
+ options: {
+ sourceMap: this.options.build.cssSourceMap
+ }
+ }, loader)
+ })
+
+ // https://github.com/postcss/postcss-loader
+ let postcssLoader
+ if (!isVueLoader && this.options.build.postcss) {
+ postcssLoader = {
+ loader: 'postcss-loader',
+ options: this.options.build.postcss
+ }
+ }
+
+ // https://github.com/webpack-contrib/css-loader
+ const cssLoader = {
+ loader: 'css-loader',
+ options: {
+ minimize: true,
+ importLoaders: 1,
+ sourceMap: this.options.build.cssSourceMap,
+ alias: {
+ '/static': join(this.options.srcDir, 'static'),
+ '/assets': join(this.options.srcDir, 'assets')
+ }
+ }
+ }
+
+ // https://github.com/vuejs/vue-style-loader
+ const vueStyleLoader = {
+ loader: 'vue-style-loader',
+ options: {
+ sourceMap: this.options.build.cssSourceMap
+ }
+ }
+
+ if (this.options.build.extractCSS && !this.options.dev) {
+ return ExtractTextPlugin.extract({
+ fallback: vueStyleLoader,
+ use: [
+ cssLoader,
+ postcssLoader,
+ ...loaders
+ ].filter(l => l)
+ })
+ }
+
+ return [
+ vueStyleLoader,
+ cssLoader,
+ postcssLoader,
+ ...loaders
+ ].filter(l => l)
+}
diff --git a/lib/builder/webpack/timefix-plugin.js b/lib/builder/webpack/timefix-plugin.js
new file mode 100644
index 0000000000..9c16918735
--- /dev/null
+++ b/lib/builder/webpack/timefix-plugin.js
@@ -0,0 +1,18 @@
+// Taken from https://github.com/egoist/poi/blob/3e93c88c520db2d20c25647415e6ae0d3de61145/packages/poi/lib/webpack/timefix-plugin.js#L1-L16
+// Thanks to @egoist
+export default class TimeFixPlugin {
+ constructor(timefix = 11000) {
+ this.timefix = timefix
+ }
+
+ apply(compiler) {
+ compiler.plugin('watch-run', (watching, callback) => {
+ watching.startTime += this.timefix
+ callback()
+ })
+
+ compiler.plugin('done', stats => {
+ stats.startTime -= this.timefix
+ })
+ }
+}
diff --git a/lib/builder/webpack/vue-loader.config.js b/lib/builder/webpack/vue-loader.config.js
new file mode 100644
index 0000000000..c5b608b6cf
--- /dev/null
+++ b/lib/builder/webpack/vue-loader.config.js
@@ -0,0 +1,34 @@
+export default function vueLoader() {
+ // https://vue-loader.vuejs.org/en
+ const config = {
+ postcss: this.options.build.postcss,
+ extractCSS: !!this.options.build.extractCSS,
+ cssSourceMap: this.options.build.cssSourceMap,
+ preserveWhitespace: false,
+ loaders: {
+ 'js': {
+ loader: 'babel-loader',
+ options: Object.assign({}, this.babelOptions)
+ },
+ // Note: do not nest the `postcss` option under `loaders`
+ 'css': this.styleLoader('css', [], true),
+ 'less': this.styleLoader('less', 'less-loader', true),
+ 'scss': this.styleLoader('scss', 'sass-loader', true),
+ 'sass': this.styleLoader('sass', {loader: 'sass-loader', options: { indentedSyntax: true }}, true),
+ 'stylus': this.styleLoader('stylus', 'stylus-loader', true),
+ 'styl': this.styleLoader('stylus', 'stylus-loader', true)
+ },
+ template: {
+ doctype: 'html' // For pug, see https://github.com/vuejs/vue-loader/issues/55
+ },
+ transformToRequire: {
+ video: 'src',
+ source: 'src',
+ object: 'src',
+ embed: 'src'
+ }
+ }
+
+ // Return the config
+ return config
+}
diff --git a/lib/common/cli/errors.js b/lib/common/cli/errors.js
new file mode 100644
index 0000000000..6a95d886d5
--- /dev/null
+++ b/lib/common/cli/errors.js
@@ -0,0 +1,29 @@
+const PrettyError = require('pretty-error')
+
+// Start default instance
+const pe = PrettyError.start()
+
+// Configure prettyError instance
+pe.skipPackage('regenerator-runtime')
+pe.skipPackage('babel-runtime')
+pe.skipPackage('core-js')
+
+// Skip dist artifacts and Node internals
+const skipFiles = [ 'nuxt.js', 'core.js' ]
+pe.skip((traceLine, lineNumber) => {
+ if (!traceLine.file || skipFiles.indexOf(traceLine.file) !== -1) {
+ return true
+ }
+})
+
+pe.skipNodeFiles()
+
+// Console error unhandled promises
+process.on('unhandledRejection', function (err) {
+ /* eslint-disable no-console */
+ console.log(pe.render(err))
+})
+
+module.exports = {
+ pe
+}
diff --git a/lib/common/index.js b/lib/common/index.js
new file mode 100755
index 0000000000..26a5ba474a
--- /dev/null
+++ b/lib/common/index.js
@@ -0,0 +1,12 @@
+import * as Utils from './utils'
+import Options from './options'
+
+export default {
+ Utils,
+ Options
+}
+
+export {
+ Utils,
+ Options
+}
diff --git a/lib/common/options.js b/lib/common/options.js
new file mode 100755
index 0000000000..3cb1e200eb
--- /dev/null
+++ b/lib/common/options.js
@@ -0,0 +1,316 @@
+import _ from 'lodash'
+import Debug from 'debug'
+import { join, resolve } from 'path'
+import { existsSync } from 'fs'
+import { isUrl, isPureObject } from 'utils'
+
+const debug = Debug('nuxt:build')
+debug.color = 2 // Force green color
+
+const Options = {}
+
+export default Options
+
+Options.from = function (_options) {
+ // Clone options to prevent unwanted side-effects
+ const options = Object.assign({}, _options)
+
+ // Normalize options
+ if (options.loading === true) {
+ delete options.loading
+ }
+ if (options.router && options.router.middleware && !Array.isArray(options.router.middleware)) {
+ options.router.middleware = [options.router.middleware]
+ }
+ if (options.router && typeof options.router.base === 'string') {
+ options._routerBaseSpecified = true
+ }
+ if (typeof options.transition === 'string') {
+ options.transition = { name: options.transition }
+ }
+ if (typeof options.layoutTransition === 'string') {
+ options.layoutTransition = { name: options.layoutTransition }
+ }
+
+ // Apply defaults
+ _.defaultsDeep(options, Options.defaults)
+
+ // Resolve dirs
+ const hasValue = v => typeof v === 'string' && v
+ options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd()
+ options.srcDir = hasValue(options.srcDir) ? resolve(options.rootDir, options.srcDir) : options.rootDir
+ options.modulesDir = resolve(options.rootDir, hasValue(options.modulesDir) ? options.modulesDir : 'node_modules')
+ options.buildDir = resolve(options.rootDir, options.buildDir)
+ options.cacheDir = resolve(options.rootDir, options.cacheDir)
+
+ // If app.html is defined, set the template path to the user template
+ options.appTemplatePath = resolve(options.buildDir, 'views/app.template.html')
+ if (existsSync(join(options.srcDir, 'app.html'))) {
+ options.appTemplatePath = join(options.srcDir, 'app.html')
+ }
+
+ // Ignore publicPath on dev
+ /* istanbul ignore if */
+ if (options.dev && isUrl(options.build.publicPath)) {
+ options.build.publicPath = Options.defaults.build.publicPath
+ }
+
+ // If store defined, update store options to true unless explicitly disabled
+ if (options.store !== false && existsSync(join(options.srcDir, 'store'))) {
+ options.store = true
+ }
+
+ // Normalize loadingIndicator
+ if (!isPureObject(options.loadingIndicator)) {
+ options.loadingIndicator = { name: options.loadingIndicator }
+ }
+
+ // Apply defaults to loadingIndicator
+ options.loadingIndicator = Object.assign({
+ name: 'pulse',
+ color: '#dbe1ec',
+ background: 'white'
+ }, options.loadingIndicator)
+
+ // cssSourceMap
+ if (options.build.cssSourceMap === undefined) {
+ options.build.cssSourceMap = options.dev
+ }
+
+ // Postcss
+ // 1. Check if it is explicitly disabled by false value
+ // ... Disable all postcss loaders
+ // 2. Check if any standard source of postcss config exists
+ // ... Make postcss = { config: { path } }
+ // 3. Else (Easy Usage)
+ // ... Auto merge it with defaults
+ if (options.build.postcss !== false) {
+ // Detect postcss config existence
+ // https://github.com/michael-ciniawsky/postcss-load-config
+ let postcssConfigPath
+ for (let dir of [options.srcDir, options.rootDir]) {
+ for (let file of ['postcss.config.js', '.postcssrc.js', '.postcssrc', '.postcssrc.json', '.postcssrc.yaml']) {
+ if (existsSync(resolve(dir, file))) {
+ postcssConfigPath = resolve(dir, file)
+ break
+ }
+ }
+ if (postcssConfigPath) break
+ }
+
+ // Default postcss options
+ if (postcssConfigPath) {
+ debug(`Using PostCSS config file: ${postcssConfigPath}`)
+ options.build.postcss = {
+ sourceMap: options.build.cssSourceMap,
+ // https://github.com/postcss/postcss-loader/blob/master/lib/index.js#L79
+ config: {
+ path: postcssConfigPath
+ }
+ }
+ } else {
+ if (Object.keys(options.build.postcss).length) {
+ debug('Using PostCSS config from `build.postcss`')
+ }
+ // Normalize & Apply default plugins
+ if (Array.isArray(options.build.postcss)) {
+ options.build.postcss = { plugins: options.build.postcss }
+ }
+ if (isPureObject(options.build.postcss)) {
+ options.build.postcss = Object.assign({
+ sourceMap: options.build.cssSourceMap,
+ plugins: {
+ // https://github.com/postcss/postcss-import
+ 'postcss-import': {
+ root: options.rootDir,
+ path: [
+ options.srcDir,
+ options.rootDir,
+ options.modulesDir
+ ]
+ },
+ // https://github.com/postcss/postcss-url
+ 'postcss-url': {},
+ // http://cssnext.io/postcss
+ 'postcss-cssnext': {}
+ }
+ }, options.build.postcss)
+ }
+ }
+ }
+
+ // Debug errors
+ if (options.debug === undefined) {
+ options.debug = options.dev
+ }
+
+ // Apply mode preset
+ let modePreset = Options.modes[options.mode || 'universal'] || Options.modes['universal']
+ _.defaultsDeep(options, modePreset)
+
+ // If no server-side rendering, add appear true transition
+ if (options.render.ssr === false && options.transition) {
+ options.transition.appear = true
+ }
+
+ return options
+}
+
+Options.modes = {
+ universal: {
+ build: {
+ ssr: true
+ },
+ render: {
+ ssr: true
+ }
+ },
+ spa: {
+ build: {
+ ssr: false
+ },
+ render: {
+ ssr: false
+ }
+ }
+}
+
+Options.defaults = {
+ mode: 'universal',
+ dev: process.env.NODE_ENV !== 'production',
+ debug: undefined, // Will be equal to dev if not provided
+ buildDir: '.nuxt',
+ cacheDir: '.cache',
+ nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist
+ build: {
+ analyze: false,
+ dll: false,
+ scopeHoisting: false,
+ extractCSS: false,
+ cssSourceMap: undefined,
+ ssr: undefined,
+ publicPath: '/_nuxt/',
+ filenames: {
+ css: 'vendor.[contenthash].css',
+ manifest: 'manifest.[hash].js',
+ vendor: 'vendor.[chunkhash].js',
+ app: 'app.[chunkhash].js',
+ chunk: '[name].[chunkhash].js'
+ },
+ vendor: [],
+ plugins: [],
+ babel: {},
+ postcss: {},
+ templates: [],
+ watch: [],
+ devMiddleware: {},
+ hotMiddleware: {}
+ },
+ generate: {
+ dir: 'dist',
+ routes: [],
+ concurrency: 500,
+ interval: 0,
+ subFolders: true,
+ minify: {
+ collapseBooleanAttributes: true,
+ collapseWhitespace: false,
+ decodeEntities: true,
+ minifyCSS: true,
+ minifyJS: true,
+ processConditionalComments: true,
+ removeAttributeQuotes: false,
+ removeComments: false,
+ removeEmptyAttributes: true,
+ removeOptionalTags: true,
+ removeRedundantAttributes: true,
+ removeScriptTypeAttributes: false,
+ removeStyleLinkTypeAttributes: false,
+ removeTagWhitespace: false,
+ sortAttributes: true,
+ sortClassName: false,
+ trimCustomFragments: true,
+ useShortDoctype: true
+ }
+ },
+ env: {},
+ head: {
+ meta: [],
+ link: [],
+ style: [],
+ script: []
+ },
+ plugins: [],
+ css: [],
+ modules: [],
+ layouts: {},
+ serverMiddleware: [],
+ ErrorPage: null,
+ loading: {
+ color: 'black',
+ failedColor: 'red',
+ height: '2px',
+ duration: 5000,
+ rtl: false
+ },
+ loadingIndicator: {},
+ transition: {
+ name: 'page',
+ mode: 'out-in',
+ appear: false,
+ appearClass: 'appear',
+ appearActiveClass: 'appear-active',
+ appearToClass: 'appear-to'
+ },
+ layoutTransition: {
+ name: 'layout',
+ mode: 'out-in'
+ },
+ router: {
+ mode: 'history',
+ base: '/',
+ routes: [],
+ middleware: [],
+ linkActiveClass: 'nuxt-link-active',
+ linkExactActiveClass: 'nuxt-link-exact-active',
+ extendRoutes: null,
+ scrollBehavior: null,
+ parseQuery: false,
+ stringifyQuery: false,
+ fallback: false
+ },
+ render: {
+ bundleRenderer: {},
+ resourceHints: true,
+ ssr: undefined,
+ http2: {
+ push: false
+ },
+ static: {},
+ gzip: {
+ threshold: 0
+ },
+ etag: {
+ weak: true // Faster for responses > 5KB
+ }
+ },
+ watchers: {
+ webpack: {
+ ignored: /-dll/
+ },
+ chokidar: {}
+ },
+ editor: {
+ editor: 'code'
+ },
+ hooks: null,
+ messages: {
+ error_404: 'This page could not be found',
+ server_error: 'Server error',
+ nuxtjs: 'Nuxt.js',
+ back_to_home: 'Back to the home page',
+ server_error_details: 'An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.',
+ client_error: 'Error',
+ client_error_details: 'An error occurred while rendering the page. Check developer tools console for details.'
+ }
+}
diff --git a/lib/common/utils.js b/lib/common/utils.js
new file mode 100644
index 0000000000..4932974119
--- /dev/null
+++ b/lib/common/utils.js
@@ -0,0 +1,268 @@
+import { resolve, relative, sep } from 'path'
+import _ from 'lodash'
+
+export function encodeHtml(str) {
+ return str.replace(//g, '>')
+}
+
+export function getContext(req, res) {
+ return { req, res }
+}
+
+export function setAnsiColors(ansiHTML) {
+ ansiHTML.setColors({
+ reset: ['efefef', 'a6004c'],
+ darkgrey: '5a012b',
+ yellow: 'ffab07',
+ green: 'aeefba',
+ magenta: 'ff84bf',
+ blue: '3505a0',
+ cyan: '56eaec',
+ red: '4e053a'
+ })
+}
+
+export async function waitFor(ms) {
+ return new Promise(function (resolve) {
+ setTimeout(resolve, (ms || 0))
+ })
+}
+
+export function urlJoin() {
+ return [].slice.call(arguments).join('/').replace(/\/+/g, '/').replace(':/', '://')
+}
+
+export function isUrl(url) {
+ return (url.indexOf('http') === 0 || url.indexOf('//') === 0)
+}
+
+export function promisifyRoute(fn) {
+ // If routes is an array
+ if (Array.isArray(fn)) {
+ return Promise.resolve(fn)
+ }
+ // If routes is a function expecting a callback
+ if (fn.length === 1) {
+ return new Promise((resolve, reject) => {
+ fn(function (err, routeParams) {
+ if (err) {
+ reject(err)
+ }
+ resolve(routeParams)
+ })
+ })
+ }
+ let promise = fn()
+ if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) {
+ promise = Promise.resolve(promise)
+ }
+ return promise
+}
+
+export function sequence(tasks, fn) {
+ return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve())
+}
+
+export function parallel(tasks, fn) {
+ return Promise.all(tasks.map(task => fn(task)))
+}
+
+export function chainFn(base, fn) {
+ /* istanbul ignore if */
+ if (!(fn instanceof Function)) {
+ return
+ }
+ return function () {
+ if (typeof base !== 'function') {
+ return fn.apply(this, arguments)
+ }
+ let baseResult = base.apply(this, arguments)
+ // Allow function to mutate the first argument instead of returning the result
+ if (baseResult === undefined) {
+ baseResult = arguments[0]
+ }
+ let fnResult = fn.call(this, baseResult, ...Array.prototype.slice.call(arguments, 1))
+ // Return mutated argument if no result was returned
+ if (fnResult === undefined) {
+ return baseResult
+ }
+ return fnResult
+ }
+}
+
+export function isPureObject(o) {
+ return !Array.isArray(o) && typeof o === 'object'
+}
+
+export const isWindows = /^win/.test(process.platform)
+
+export function wp(p = '') {
+ /* istanbul ignore if */
+ if (isWindows) {
+ return p.replace(/\\/g, '\\\\')
+ }
+ return p
+}
+
+export function wChunk(p = '') {
+ /* istanbul ignore if */
+ if (isWindows) {
+ return p.replace(/\//g, '\\\\')
+ }
+ return p
+}
+
+const reqSep = /\//g
+const sysSep = _.escapeRegExp(sep)
+const normalize = string => string.replace(reqSep, sysSep)
+
+export function r() {
+ let args = Array.prototype.slice.apply(arguments)
+ let lastArg = _.last(args)
+
+ if (lastArg.includes('@') || lastArg.includes('~')) {
+ return wp(lastArg)
+ }
+
+ return wp(resolve(...args.map(normalize)))
+}
+
+export function relativeTo() {
+ let args = Array.prototype.slice.apply(arguments)
+ let dir = args.shift()
+
+ // Resolve path
+ let path = r(...args)
+
+ // Check if path is an alias
+ if (path.includes('@') || path.includes('~')) {
+ return path
+ }
+
+ // Make correct relative path
+ let rp = relative(dir, path)
+ if (rp[0] !== '.') {
+ rp = './' + rp
+ }
+ return wp(rp)
+}
+
+export function flatRoutes(router, path = '', routes = []) {
+ router.forEach((r) => {
+ if (!r.path.includes(':') && !r.path.includes('*')) {
+ /* istanbul ignore if */
+ if (r.children) {
+ flatRoutes(r.children, path + r.path + '/', routes)
+ } else {
+ routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
+ }
+ }
+ })
+ return routes
+}
+
+export function cleanChildrenRoutes(routes, isChild = false) {
+ let start = -1
+ let routesIndex = []
+ routes.forEach((route) => {
+ if (/-index$/.test(route.name) || route.name === 'index') {
+ // Save indexOf 'index' key in name
+ let res = route.name.split('-')
+ let s = res.indexOf('index')
+ start = (start === -1 || s < start) ? s : start
+ routesIndex.push(res)
+ }
+ })
+ routes.forEach((route) => {
+ route.path = (isChild) ? route.path.replace('/', '') : route.path
+ if (route.path.indexOf('?') > -1) {
+ let names = route.name.split('-')
+ let paths = route.path.split('/')
+ if (!isChild) {
+ paths.shift()
+ } // clean first / for parents
+ routesIndex.forEach((r) => {
+ let i = r.indexOf('index') - start // children names
+ if (i < paths.length) {
+ for (let a = 0; a <= i; a++) {
+ if (a === i) {
+ paths[a] = paths[a].replace('?', '')
+ }
+ if (a < i && names[a] !== r[a]) {
+ break
+ }
+ }
+ }
+ })
+ route.path = (isChild ? '' : '/') + paths.join('/')
+ }
+ route.name = route.name.replace(/-index$/, '')
+ if (route.children) {
+ if (route.children.find((child) => child.path === '')) {
+ delete route.name
+ }
+ route.children = cleanChildrenRoutes(route.children, true)
+ }
+ })
+ return routes
+}
+
+export function createRoutes(files, srcDir) {
+ let routes = []
+ files.forEach((file) => {
+ let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
+ let route = { name: '', path: '', component: r(srcDir, file) }
+ let parent = routes
+ keys.forEach((key, i) => {
+ route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '')
+ route.name += (key === '_') ? 'all' : ''
+ route.chunkName = file.replace(/\.vue$/, '')
+ let child = _.find(parent, { name: route.name })
+ if (child) {
+ child.children = child.children || []
+ parent = child.children
+ route.path = ''
+ } else {
+ if (key === 'index' && (i + 1) === keys.length) {
+ route.path += (i > 0 ? '' : '/')
+ } else {
+ route.path += '/' + (key === '_' ? '*' : key.replace('_', ':'))
+ if (key !== '_' && key.indexOf('_') !== -1) {
+ route.path += '?'
+ }
+ }
+ }
+ })
+ // Order Routes path
+ parent.push(route)
+ parent.sort((a, b) => {
+ if (!a.path.length || a.path === '/') {
+ return -1
+ }
+ if (!b.path.length || b.path === '/') {
+ return 1
+ }
+ let i = 0
+ let res = 0
+ let y = 0
+ let z = 0
+ const _a = a.path.split('/')
+ const _b = b.path.split('/')
+ for (i = 0; i < _a.length; i++) {
+ if (res !== 0) {
+ break
+ }
+ y = _a[i] === '*' ? 2 : (_a[i].indexOf(':') > -1 ? 1 : 0)
+ z = _b[i] === '*' ? 2 : (_b[i].indexOf(':') > -1 ? 1 : 0)
+ res = y - z
+ // If a.length >= b.length
+ if (i === _b.length - 1 && res === 0) {
+ // change order if * found
+ res = _a[i] === '*' ? -1 : 1
+ }
+ }
+ return res === 0 ? (_a[i - 1] === '*' && _b[i] ? 1 : -1) : res
+ })
+ })
+ return cleanChildrenRoutes(routes)
+}
diff --git a/lib/core/index.js b/lib/core/index.js
new file mode 100755
index 0000000000..3eee8a295c
--- /dev/null
+++ b/lib/core/index.js
@@ -0,0 +1,12 @@
+import { Options, Utils } from 'common'
+import Module from './module'
+import Nuxt from './nuxt'
+import Renderer from './renderer'
+
+export {
+ Nuxt,
+ Module,
+ Renderer,
+ Options,
+ Utils
+}
diff --git a/lib/core/meta.js b/lib/core/meta.js
new file mode 100644
index 0000000000..8264a506ab
--- /dev/null
+++ b/lib/core/meta.js
@@ -0,0 +1,86 @@
+
+import Vue from 'vue'
+import VueMeta from 'vue-meta'
+import VueServerRenderer from 'vue-server-renderer'
+import LRU from 'lru-cache'
+
+export default class MetaRenderer {
+ constructor(nuxt, renderer) {
+ this.nuxt = nuxt
+ this.renderer = renderer
+ this.options = nuxt.options
+ this.vueRenderer = VueServerRenderer.createRenderer()
+ this.cache = LRU({})
+
+ // Add VueMeta to Vue (this is only for SPA mode)
+ // See lib/app/index.js
+ Vue.use(VueMeta, {
+ keyName: 'head',
+ attribute: 'data-n-head',
+ ssrAttribute: 'data-n-head-ssr',
+ tagIDKeyName: 'hid'
+ })
+ }
+
+ getMeta(url) {
+ return new Promise((resolve, reject) => {
+ const vm = new Vue({
+ render: (h) => h(), // Render empty html tag
+ head: this.options.head || {}
+ })
+ this.vueRenderer.renderToString(vm, (err) => {
+ if (err) return reject(err)
+ resolve(vm.$meta().inject())
+ })
+ })
+ }
+
+ async render({ url = '/' }) {
+ let meta = this.cache.get(url)
+
+ if (meta) {
+ return meta
+ }
+
+ meta = {
+ HTML_ATTRS: '',
+ BODY_ATTRS: '',
+ HEAD: '',
+ BODY_SCRIPTS: ''
+ }
+ // Get vue-meta context
+ const m = await this.getMeta(url)
+ // HTML_ATTRS
+ meta.HTML_ATTRS = m.htmlAttrs.text()
+ // BODY_ATTRS
+ meta.BODY_ATTRS = m.bodyAttrs.text()
+ // HEAD tags
+ meta.HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
+ // BODY_SCRIPTS
+ meta.BODY_SCRIPTS = m.script.text({ body: true })
+ // Resources Hints
+ meta.resourceHints = ''
+ // Resource Hints
+ const clientManifest = this.renderer.resources.clientManifest
+ if (this.options.render.resourceHints && clientManifest) {
+ const publicPath = clientManifest.publicPath || '/_nuxt/'
+ // Pre-Load initial resources
+ if (Array.isArray(clientManifest.initial)) {
+ meta.resourceHints += clientManifest.initial.map(r => ` `).join('')
+ }
+ // Pre-Fetch async resources
+ if (Array.isArray(clientManifest.async)) {
+ meta.resourceHints += clientManifest.async.map(r => ` `).join('')
+ }
+ // Add them to HEAD
+ if (meta.resourceHints) {
+ meta.HEAD += meta.resourceHints
+ }
+ }
+
+ // Set meta tags inside cache
+ this.cache.set(url, meta)
+
+ return meta
+ }
+}
diff --git a/lib/module.js b/lib/core/module.js
similarity index 56%
rename from lib/module.js
rename to lib/core/module.js
index eed166f5fa..0ad7182279 100755
--- a/lib/module.js
+++ b/lib/core/module.js
@@ -1,32 +1,28 @@
-'use strict'
-
import path from 'path'
import fs from 'fs'
import { uniq } from 'lodash'
import hash from 'hash-sum'
-import { chainFn, sequence } from './utils'
+import { chainFn, sequence } from 'utils'
+import Debug from 'debug'
-const debug = require('debug')('nuxt:module')
+const debug = Debug('nuxt:module')
-class Module {
- constructor (nuxt) {
+export default class ModuleContainer {
+ constructor(nuxt) {
this.nuxt = nuxt
this.options = nuxt.options
this.requiredModules = []
- this.initing = this.ready()
}
- async ready () {
- if (this.initing) {
- await this.initing
- return this
- }
- // Install all modules in sequence
+ async ready() {
+ await this.nuxt.callHook('modules:before', this, this.options.modules)
+ // Load every module in sequence
await sequence(this.options.modules, this.addModule.bind(this))
- return this
+ // Call done hook
+ await this.nuxt.callHook('modules:done', this)
}
- addVendor (vendor) {
+ addVendor(vendor) {
/* istanbul ignore if */
if (!vendor) {
return
@@ -34,7 +30,7 @@ class Module {
this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
}
- addTemplate (template) {
+ addTemplate(template) {
/* istanbul ignore if */
if (!template) {
return
@@ -44,6 +40,7 @@ class Module {
const srcPath = path.parse(src)
/* istanbul ignore if */
if (!src || typeof src !== 'string' || !fs.existsSync(src)) {
+ /* istanbul ignore next */
debug('[nuxt] invalid template', template)
return
}
@@ -60,37 +57,38 @@ class Module {
return templateObj
}
- addPlugin (template) {
- const {dst} = this.addTemplate(template)
+ addPlugin(template) {
+ const { dst } = this.addTemplate(template)
// Add to nuxt plugins
this.options.plugins.unshift({
- src: path.join(this.nuxt.buildDir, dst),
+ src: path.join(this.options.buildDir, dst),
ssr: template.ssr
})
}
- addServerMiddleware (middleware) {
+ addServerMiddleware(middleware) {
this.options.serverMiddleware.push(middleware)
}
- extendBuild (fn) {
+ extendBuild(fn) {
this.options.build.extend = chainFn(this.options.build.extend, fn)
}
- extendRoutes (fn) {
+ extendRoutes(fn) {
this.options.router.extendRoutes = chainFn(this.options.router.extendRoutes, fn)
}
- requireModule (moduleOpts) {
+ requireModule(moduleOpts) {
// Require once
return this.addModule(moduleOpts, true)
}
- addModule (moduleOpts, requireOnce) {
+ async addModule(moduleOpts, requireOnce) {
/* istanbul ignore if */
if (!moduleOpts) {
return
}
+
// Allow using babel style array options
if (Array.isArray(moduleOpts)) {
moduleOpts = {
@@ -98,55 +96,44 @@ class Module {
options: moduleOpts[1]
}
}
+
// Allows passing runtime options to each module
const options = moduleOpts.options || (typeof moduleOpts === 'object' ? moduleOpts : {})
- const originalSrc = moduleOpts.src || moduleOpts
+ const src = moduleOpts.src || moduleOpts
+
// Resolve module
- let module = originalSrc
- try {
- if (typeof module === 'string') {
- // Using ~ or ./ shorthand modules are resolved from project srcDir
- if (module.indexOf('~') === 0 || module.indexOf('./') === 0) {
- module = path.join(this.options.srcDir, module.substr(1))
- }
- // eslint-disable-next-line no-eval
- module = eval('require')(module)
- }
- } catch (e) /* istanbul ignore next */ {
- // eslint-disable-next-line no-console
- console.error('[nuxt] Unable to resolve module', module)
- // eslint-disable-next-line no-console
- console.error(e)
- process.exit(0)
+ let module
+ if (typeof src === 'string') {
+ module = require(this.nuxt.resolvePath(src))
}
+
// Validate module
/* istanbul ignore if */
if (typeof module !== 'function') {
- // eslint-disable-next-line no-console
- console.error(`[nuxt] Module [${originalSrc}] should export a function`)
- process.exit(1)
+ throw new Error(`[nuxt] Module ${JSON.stringify(src)} should export a function`)
}
+
// Module meta
- if (!module.meta) {
- module.meta = {}
- }
- if (module.meta.name) {
- const alreadyRequired = this.requiredModules.indexOf(module.meta.name) !== -1
- if (requireOnce && alreadyRequired) {
+ module.meta = module.meta || {}
+ let name = module.meta.name || module.name
+
+ // If requireOnce specified & module from NPM or with specified name
+ if (requireOnce && name) {
+ const alreadyRequired = this.requiredModules.indexOf(name) !== -1
+ if (alreadyRequired) {
return
}
- if (!alreadyRequired) {
- this.requiredModules.push(module.meta.name)
- }
+ this.requiredModules.push(name)
}
+
// Call module with `this` context and pass options
- return new Promise((resolve, reject) => {
- const result = module.call(this, options, err => {
+ await new Promise((resolve, reject) => {
+ const result = module.call(this, options, (err) => {
/* istanbul ignore if */
if (err) {
return reject(err)
}
- resolve(module)
+ resolve()
})
// If module send back a promise
if (result && result.then instanceof Function) {
@@ -154,10 +141,8 @@ class Module {
}
// If not expecting a callback but returns no promise (=synchronous)
if (module.length < 2) {
- return resolve(module)
+ return resolve()
}
})
}
}
-
-export default Module
diff --git a/lib/core/nuxt.js b/lib/core/nuxt.js
new file mode 100644
index 0000000000..5ba85a2958
--- /dev/null
+++ b/lib/core/nuxt.js
@@ -0,0 +1,191 @@
+import chalk from 'chalk'
+import { Options } from 'common'
+import { sequence } from 'utils'
+import ModuleContainer from './module'
+import Renderer from './renderer'
+import Debug from 'debug'
+import enableDestroy from 'server-destroy'
+import Module from 'module'
+import { isPlainObject } from 'lodash'
+import { join, resolve } from 'path'
+
+const debug = Debug('nuxt:')
+debug.color = 5
+
+export default class Nuxt {
+ constructor(options = {}) {
+ this.options = Options.from(options)
+
+ // Paths for resolving requires from `rootDir`
+ this.nodeModulePaths = Module._nodeModulePaths(this.options.rootDir)
+
+ this.initialized = false
+ this.errorHandler = this.errorHandler.bind(this)
+ // Hooks
+ this._hooks = {}
+ this.hook = this.hook.bind(this)
+
+ // Create instance of core components
+ this.moduleContainer = new ModuleContainer(this)
+ this.renderer = new Renderer(this)
+
+ // Backward compatibility
+ this.render = this.renderer.app
+ this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
+ this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(this.renderer)
+
+ this._ready = this.ready().catch(this.errorHandler)
+ }
+
+ async ready() {
+ if (this._ready) {
+ return this._ready
+ }
+
+ // Add hooks
+ if (isPlainObject(this.options.hooks)) {
+ this.addObjectHooks(this.options.hooks)
+ } else if (typeof this.options.hooks === 'function') {
+ this.options.hooks(this.hook)
+ }
+ // Add nuxt modules
+ await this.moduleContainer.ready()
+ await this.renderer.ready()
+
+ this.initialized = true
+ await this.callHook('ready', this)
+
+ return this
+ }
+
+ plugin(name, fn) {
+ // eslint-disable-next-line no-console
+ console.error(`[warn] nuxt.plugin('${name}',..) is deprecated. Please use new hooks system.`)
+
+ // A tiny backward compatibility util
+ const hookMap = {
+ 'ready': 'ready',
+ 'close': 'close',
+ 'listen': 'listen',
+ 'built': 'build:done'
+ }
+
+ if (hookMap[name]) {
+ this.hook(hookMap[name], fn)
+ }
+
+ // Always return nuxt class which has plugin() for two level hooks
+ return this
+ }
+
+ hook(name, fn) {
+ if (!name || typeof fn !== 'function') {
+ return
+ }
+ this._hooks[name] = this._hooks[name] || []
+ this._hooks[name].push(fn)
+ }
+
+ async callHook(name, ...args) {
+ if (!this._hooks[name]) {
+ return
+ }
+ debug(`Call ${name} hooks (${this._hooks[name].length})`)
+ try {
+ await sequence(this._hooks[name], (fn) => fn(...args))
+ } catch (err) {
+ console.error(`> Error on hook "${name}":`) // eslint-disable-line no-console
+ console.error(err) // eslint-disable-line no-console
+ }
+ }
+
+ addObjectHooks(hooksObj) {
+ Object.keys(hooksObj).forEach((name) => {
+ let hooks = hooksObj[name]
+ hooks = (Array.isArray(hooks) ? hooks : [hooks])
+
+ hooks.forEach((hook) => {
+ this.hook(name, hook)
+ })
+ })
+ }
+
+ listen(port = 3000, host = 'localhost') {
+ return new Promise((resolve, reject) => {
+ const server = this.renderer.app.listen({ port, host, exclusive: false }, (err) => {
+ /* istanbul ignore if */
+ if (err) {
+ return reject(err)
+ }
+
+ const _host = host === '0.0.0.0' ? 'localhost' : host
+ // eslint-disable-next-line no-console
+ console.log('\n' + chalk.bgGreen.black(' OPEN ') + chalk.green(` http://${_host}:${port}\n`))
+
+ // Close server on nuxt close
+ this.hook('close', () => new Promise((resolve, reject) => {
+ // Destroy server by forcing every connection to be closed
+ server.destroy(err => {
+ debug('server closed')
+ /* istanbul ignore if */
+ if (err) {
+ return reject(err)
+ }
+ resolve()
+ })
+ }))
+
+ this.callHook('listen', server, { port, host }).then(resolve)
+ })
+
+ // Add server.destroy(cb) method
+ enableDestroy(server)
+ })
+ }
+
+ errorHandler/* istanbul ignore next */() {
+ // Apply plugins
+ // eslint-disable-next-line no-console
+ this.callHook('error', ...arguments).catch(console.error)
+
+ // Silent
+ if (this.options.errorHandler === false) {
+ return
+ }
+
+ // Custom errorHandler
+ if (typeof this.options.errorHandler === 'function') {
+ return this.options.errorHandler.apply(this, arguments)
+ }
+
+ // Default handler
+ // eslint-disable-next-line no-console
+ console.error(...arguments)
+ }
+
+ resolvePath(path) {
+ // Try to resolve using NPM resolve path first
+ try {
+ let resolvedPath = Module._resolveFilename(path, { paths: this.nodeModulePaths })
+ return resolvedPath
+ } catch (e) {
+ // Just continue
+ }
+ // Shorthand to resolve from project dirs
+ if (path.indexOf('@@') === 0 || path.indexOf('~~') === 0) {
+ return join(this.options.rootDir, path.substr(2))
+ } else if (path.indexOf('@') === 0 || path.indexOf('~') === 0) {
+ return join(this.options.srcDir, path.substr(1))
+ }
+ return resolve(this.options.srcDir, path)
+ }
+
+ async close(callback) {
+ await this.callHook('close', this)
+
+ /* istanbul ignore if */
+ if (typeof callback === 'function') {
+ await callback()
+ }
+ }
+}
diff --git a/lib/core/renderer.js b/lib/core/renderer.js
new file mode 100644
index 0000000000..9b39b97a28
--- /dev/null
+++ b/lib/core/renderer.js
@@ -0,0 +1,623 @@
+import ansiHTML from 'ansi-html'
+import serialize from 'serialize-javascript'
+import generateETag from 'etag'
+import fresh from 'fresh'
+import serveStatic from 'serve-static'
+import compression from 'compression'
+import _ from 'lodash'
+import { join, resolve } from 'path'
+import fs from 'fs-extra'
+import { createBundleRenderer } from 'vue-server-renderer'
+import { getContext, setAnsiColors, isUrl } from 'utils'
+import Debug from 'debug'
+import Youch from '@nuxtjs/youch'
+import { SourceMapConsumer } from 'source-map'
+import connect from 'connect'
+import { Options } from 'common'
+import MetaRenderer from './meta'
+
+const debug = Debug('nuxt:render')
+debug.color = 4 // Force blue color
+
+setAnsiColors(ansiHTML)
+
+let jsdom = null
+
+export default class Renderer {
+ constructor(nuxt) {
+ this.nuxt = nuxt
+ this.options = nuxt.options
+
+ // Will be set by createRenderer
+ this.bundleRenderer = null
+ this.metaRenderer = null
+
+ // Will be available on dev
+ this.webpackDevMiddleware = null
+ this.webpackHotMiddleware = null
+
+ // Create new connect instance
+ this.app = connect()
+
+ // Renderer runtime resources
+ this.resources = {
+ clientManifest: null,
+ serverBundle: null,
+ ssrTemplate: null,
+ spaTemplate: null,
+ errorTemplate: parseTemplate('Nuxt.js Internal Server Error')
+ }
+ }
+
+ async ready() {
+ await this.nuxt.callHook('render:before', this, this.options.render)
+ // Setup nuxt middleware
+ await this.setupMiddleware()
+
+ // Production: Load SSR resources from fs
+ if (!this.options.dev) {
+ await this.loadResources()
+ }
+
+ // Call done hook
+ await this.nuxt.callHook('render:done', this)
+ }
+
+ async loadResources(_fs = fs) {
+ let distPath = resolve(this.options.buildDir, 'dist')
+ let updated = []
+
+ resourceMap.forEach(({ key, fileName, transform }) => {
+ let rawKey = '$$' + key
+ const path = join(distPath, fileName)
+
+ let rawData, data
+ if (!_fs.existsSync(path)) {
+ return // Resource not exists
+ }
+ rawData = _fs.readFileSync(path, 'utf8')
+ if (!rawData || rawData === this.resources[rawKey]) {
+ return // No changes
+ }
+ this.resources[rawKey] = rawData
+ data = transform(rawData)
+ /* istanbul ignore if */
+ if (!data) {
+ return // Invalid data ?
+ }
+ this.resources[key] = data
+ updated.push(key)
+ })
+
+ // Reload error template
+ const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html')
+ if (fs.existsSync(errorTemplatePath)) {
+ this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8'))
+ }
+
+ // Load loading template
+ const loadingHTMLPath = resolve(this.options.buildDir, 'loading.html')
+ if (fs.existsSync(loadingHTMLPath)) {
+ this.resources.loadingHTML = fs.readFileSync(loadingHTMLPath, 'utf8')
+ this.resources.loadingHTML = this.resources.loadingHTML.replace(/[\r|\n]/g, '')
+ } else {
+ this.resources.loadingHTML = ''
+ }
+
+ // Call resourcesLoaded plugin
+ await this.nuxt.callHook('render:resourcesLoaded', this.resources)
+
+ if (updated.length > 0) {
+ this.createRenderer()
+ }
+ }
+
+ get noSSR() {
+ return this.options.render.ssr === false
+ }
+
+ get isReady() {
+ if (this.noSSR) {
+ return Boolean(this.resources.spaTemplate)
+ }
+
+ return Boolean(this.bundleRenderer && this.resources.ssrTemplate)
+ }
+
+ get isResourcesAvailable() {
+ // Required for both
+ if (!this.resources.clientManifest) {
+ return false
+ }
+
+ // Required for SPA rendering
+ if (this.noSSR) {
+ return Boolean(this.resources.spaTemplate)
+ }
+
+ // Required for bundle renderer
+ return Boolean(this.resources.ssrTemplate && this.resources.serverBundle)
+ }
+
+ createRenderer() {
+ // Ensure resources are available
+ if (!this.isResourcesAvailable) {
+ return
+ }
+
+ // Create Meta Renderer
+ this.metaRenderer = new MetaRenderer(this.nuxt, this)
+
+ // Skip following steps if noSSR mode
+ if (this.noSSR) {
+ return
+ }
+
+ // Create bundle renderer for SSR
+ this.bundleRenderer = createBundleRenderer(this.resources.serverBundle, Object.assign({
+ clientManifest: this.resources.clientManifest,
+ runInNewContext: false,
+ basedir: this.options.rootDir
+ }, this.options.render.bundleRenderer))
+ }
+
+ useMiddleware(m) {
+ // Resolve
+ const $m = m
+ let src
+ if (typeof m === 'string') {
+ src = this.nuxt.resolvePath(m)
+ m = require(src)
+ }
+ if (typeof m.handler === 'string') {
+ src = this.nuxt.resolvePath(m.handler)
+ m.handler = require(src)
+ }
+
+ const handler = m.handler || m
+ const path = (((m.prefix !== false) ? this.options.router.base : '') + (typeof m.path === 'string' ? m.path : '')).replace(/\/\//g, '/')
+
+ // Inject $src and $m to final handler
+ if (src) handler.$src = src
+ handler.$m = $m
+
+ // Use middleware
+ this.app.use(path, handler)
+ }
+
+ get publicPath() {
+ return isUrl(this.options.build.publicPath) ? Options.defaults.build.publicPath : this.options.build.publicPath
+ }
+
+ async setupMiddleware() {
+ // Apply setupMiddleware from modules first
+ await this.nuxt.callHook('render:setupMiddleware', this.app)
+
+ // Gzip middleware for production
+ if (!this.options.dev && this.options.render.gzip) {
+ this.useMiddleware(compression(this.options.render.gzip))
+ }
+
+ // Common URL checks
+ this.useMiddleware((req, res, next) => {
+ // Prevent access to SSR resources
+ if (ssrResourceRegex.test(req.url)) {
+ res.statusCode = 404
+ return res.end()
+ }
+ next()
+ })
+
+ // Add webpack middleware only for development
+ if (this.options.dev) {
+ this.useMiddleware(async (req, res, next) => {
+ if (this.webpackDevMiddleware) {
+ await this.webpackDevMiddleware(req, res)
+ }
+ if (this.webpackHotMiddleware) {
+ await this.webpackHotMiddleware(req, res)
+ }
+ next()
+ })
+ }
+
+ // open in editor for debug mode only
+ const _this = this
+ if (this.options.debug) {
+ this.useMiddleware({
+ path: '_open',
+ handler(req, res) {
+ // Lazy load open-in-editor
+ const openInEditor = require('open-in-editor')
+ const editor = openInEditor.configure(_this.options.editor)
+ // Parse Query
+ const query = req.url.split('?')[1].split('&').reduce((q, part) => {
+ const s = part.split('=')
+ q[s[0]] = decodeURIComponent(s[1])
+ return q
+ }, {})
+ // eslint-disable-next-line no-console
+ console.log('[open in editor]', query.file)
+ editor.open(query.file).then(() => {
+ res.end('opened in editor!')
+ }).catch(err => {
+ res.end(err)
+ })
+ }
+ })
+ }
+
+ // For serving static/ files to /
+ this.useMiddleware(serveStatic(resolve(this.options.srcDir, 'static'), this.options.render.static))
+
+ // Serve .nuxt/dist/ files only for production
+ // For dev they will be served with devMiddleware
+ if (!this.options.dev) {
+ const distDir = resolve(this.options.buildDir, 'dist')
+ this.useMiddleware({
+ path: this.publicPath,
+ handler: serveStatic(distDir, {
+ index: false, // Don't serve index.html template
+ maxAge: '1y' // 1 year in production
+ })
+ })
+ }
+
+ // Add User provided middleware
+ this.options.serverMiddleware.forEach(m => {
+ this.useMiddleware(m)
+ })
+
+ // Finally use nuxtMiddleware
+ this.useMiddleware(this.nuxtMiddleware.bind(this))
+
+ // Error middleware for errors that occurred in middleware that declared above
+ // Middleware should exactly take 4 arguments
+ // https://github.com/senchalabs/connect#error-middleware
+ this.useMiddleware(this.errorMiddleware.bind(this))
+ }
+
+ async nuxtMiddleware(req, res, next) {
+ // Get context
+ const context = getContext(req, res)
+
+ res.statusCode = 200
+ try {
+ const result = await this.renderRoute(req.url, context)
+ await this.nuxt.callHook('render:route', req.url, result)
+ const { html, error, redirected, resourceHints } = result
+
+ if (redirected) {
+ return html
+ }
+ if (error) {
+ res.statusCode = context.nuxt.error.statusCode || 500
+ }
+
+ // Add ETag header
+ if (!error && this.options.render.etag) {
+ const etag = generateETag(html, this.options.render.etag)
+ if (fresh(req.headers, { etag })) {
+ res.statusCode = 304
+ res.end()
+ return
+ }
+ res.setHeader('ETag', etag)
+ }
+
+ // HTTP2 push headers
+ if (!error && this.options.render.http2.push) {
+ // Parse resourceHints to extract HTTP.2 prefetch/push headers
+ // https://w3c.github.io/preload/#server-push-http-2
+ const regex = /link rel="([^"]*)" href="([^"]*)" as="([^"]*)"/g
+ const pushAssets = []
+ let m
+ while (m = regex.exec(resourceHints)) { // eslint-disable-line no-cond-assign
+ const [, rel, href, as] = m
+ if (rel === 'preload') {
+ pushAssets.push(`<${href}>; rel=${rel}; as=${as}`)
+ }
+ }
+ // Pass with single Link header
+ // https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
+ res.setHeader('Link', pushAssets.join(','))
+ }
+
+ // Send response
+ res.setHeader('Content-Type', 'text/html; charset=utf-8')
+ res.setHeader('Content-Length', Buffer.byteLength(html))
+ res.end(html, 'utf8')
+ return html
+ } catch (err) {
+ /* istanbul ignore if */
+ if (context && context.redirected) {
+ console.error(err) // eslint-disable-line no-console
+ return err
+ }
+
+ next(err)
+ }
+ }
+
+ errorMiddleware(err, req, res, next) {
+ // ensure statusCode, message and name fields
+ err.statusCode = err.statusCode || 500
+ err.message = err.message || 'Nuxt Server Error'
+ err.name = (!err.name || err.name === 'Error') ? 'NuxtServerError' : err.name
+
+ // We hide actual errors from end users, so show them on server logs
+ if (err.statusCode !== 404) {
+ console.error(err) // eslint-disable-line no-console
+ }
+
+ const sendResponse = (content, type = 'text/html') => {
+ // Set Headers
+ res.statusCode = err.statusCode
+ res.statusMessage = err.name
+ res.setHeader('Content-Type', type + '; charset=utf-8')
+ res.setHeader('Content-Length', Buffer.byteLength(content))
+
+ // Send Response
+ res.end(content, 'utf-8')
+ }
+
+ // Check if request accepts JSON
+ const hasReqHeader = (header, includes) => req.headers[header] && req.headers[header].toLowerCase().includes(includes)
+ const isJson = hasReqHeader('accept', 'application/json') || hasReqHeader('user-agent', 'curl/')
+
+ // Use basic errors when debug mode is disabled
+ if (!this.options.debug) {
+ // Json format is compatible with Youch json responses
+ const json = {
+ status: err.statusCode,
+ message: err.message,
+ name: err.name
+ }
+ if (isJson) {
+ sendResponse(JSON.stringify(json, undefined, 2), 'text/json')
+ return
+ }
+ const html = this.resources.errorTemplate(json)
+ sendResponse(html)
+ return
+ }
+
+ // Show stack trace
+ const youch = new Youch(err, req, this.readSource.bind(this))
+ if (isJson) {
+ youch.toJSON().then(json => { sendResponse(JSON.stringify(json, undefined, 2), 'text/json') })
+ } else {
+ youch.toHTML().then(html => { sendResponse(html) })
+ }
+ }
+
+ async readSource(frame) {
+ const serverBundle = this.resources.serverBundle
+
+ // Remove webpack:/// & query string from the end
+ const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : ''
+
+ // SourceMap Support for SSR Bundle
+ if (serverBundle && serverBundle.maps[frame.fileName]) {
+ // Initialize smc cache
+ if (!serverBundle.$maps) {
+ serverBundle.$maps = {}
+ }
+
+ // Read SourceMap object
+ const smc = serverBundle.$maps[frame.fileName] || new SourceMapConsumer(serverBundle.maps[frame.fileName])
+ serverBundle.$maps[frame.fileName] = smc
+
+ // Try to find original position
+ const { line, column, name, source } = smc.originalPositionFor({
+ line: frame.getLineNumber() || 0,
+ column: frame.getColumnNumber() || 0,
+ bias: SourceMapConsumer.LEAST_UPPER_BOUND
+ })
+ if (line) {
+ frame.lineNumber = line
+ }
+ if (column) {
+ frame.columnNumber = column
+ }
+ if (name) {
+ frame.functionName = name
+ }
+ if (source) {
+ frame.fileName = sanitizeName(source)
+
+ // Source detected, try to get original source code
+ const contents = smc.sourceContentFor(source)
+ if (contents) {
+ frame.contents = contents
+ }
+ }
+ }
+
+ // Return if fileName is still unknown
+ if (!frame.fileName) {
+ return
+ }
+
+ frame.fileName = sanitizeName(frame.fileName)
+
+ // Try to read from SSR bundle files
+ if (serverBundle && serverBundle.files[frame.fileName]) {
+ frame.contents = serverBundle.files[frame.fileName]
+ return
+ }
+
+ // Possible paths for file
+ const searchPath = [
+ this.options.rootDir,
+ join(this.options.buildDir, 'dist'),
+ this.options.srcDir,
+ this.options.buildDir
+ ]
+
+ // Scan filesystem for real path
+ for (let pathDir of searchPath) {
+ let fullPath = resolve(pathDir, frame.fileName)
+ let source = await fs.readFile(fullPath, 'utf-8').catch(() => null)
+ if (source) {
+ if (!frame.contents) {
+ frame.contents = source
+ }
+ frame.fullPath = fullPath
+ return
+ }
+ }
+ }
+
+ async renderRoute(url, context = {}) {
+ /* istanbul ignore if */
+ if (!this.isReady) {
+ return new Promise(resolve => {
+ setTimeout(() => resolve(this.renderRoute(url, context)), 1000)
+ })
+ }
+
+ // Log rendered url
+ debug(`Rendering url ${url}`)
+
+ // Add url and isSever to the context
+ context.url = url
+
+ // Basic response if SSR is disabled or spa data provided
+ const spa = context.spa || (context.res && context.res.spa)
+ const ENV = this.options.env
+
+ if (this.noSSR || spa) {
+ const { HTML_ATTRS, BODY_ATTRS, HEAD, BODY_SCRIPTS, resourceHints } = await this.metaRenderer.render(context)
+ const APP = `${this.resources.loadingHTML}
` + BODY_SCRIPTS
+
+ // Detect 404 errors
+ if (url.includes(this.options.build.publicPath) || url.includes('__webpack')) {
+ const err = { statusCode: 404, message: this.options.messages.error_404, name: 'ResourceNotFound' }
+ throw err
+ }
+
+ const html = this.resources.spaTemplate({
+ HTML_ATTRS,
+ BODY_ATTRS,
+ HEAD,
+ APP,
+ ENV
+ })
+
+ return { html, resourceHints }
+ }
+
+ // Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
+ let APP = await this.bundleRenderer.renderToString(context)
+
+ if (!context.nuxt.serverRendered) {
+ APP = '
'
+ }
+ const m = context.meta.inject()
+ let HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
+ if (this.options._routerBaseSpecified) {
+ HEAD += ` `
+ }
+
+ let resourceHints = ''
+
+ if (this.options.render.resourceHints) {
+ resourceHints = context.renderResourceHints()
+ HEAD += resourceHints
+ }
+ APP += ``
+ APP += context.renderScripts()
+ APP += m.script.text({ body: true })
+
+ HEAD += context.renderStyles()
+
+ let html = this.resources.ssrTemplate({
+ HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
+ BODY_ATTRS: m.bodyAttrs.text(),
+ HEAD,
+ APP,
+ ENV
+ })
+
+ return {
+ html,
+ resourceHints,
+ error: context.nuxt.error,
+ redirected: context.redirected
+ }
+ }
+
+ async renderAndGetWindow(url, opts = {}) {
+ /* istanbul ignore if */
+ if (!jsdom) {
+ try {
+ jsdom = require('jsdom')
+ } catch (e) /* istanbul ignore next */ {
+ /* eslint-disable no-console */
+ console.error('Fail when calling nuxt.renderAndGetWindow(url)')
+ console.error('jsdom module is not installed')
+ console.error('Please install jsdom with: npm install --save-dev jsdom')
+ /* eslint-enable no-console */
+ throw e
+ }
+ }
+ let options = {
+ resources: 'usable', // load subresources (https://github.com/tmpvar/jsdom#loading-subresources)
+ runScripts: 'dangerously',
+ beforeParse(window) {
+ // Mock window.scrollTo
+ window.scrollTo = () => {}
+ }
+ }
+ if (opts.virtualConsole !== false) {
+ options.virtualConsole = new jsdom.VirtualConsole().sendTo(console)
+ }
+ url = url || 'http://localhost:3000'
+ const { window } = await jsdom.JSDOM.fromURL(url, options)
+ // If Nuxt could not be loaded (error from the server-side)
+ const nuxtExists = window.document.body.innerHTML.includes(this.options.render.ssr ? 'window.__NUXT__' : '')
+ /* istanbul ignore if */
+ if (!nuxtExists) {
+ let error = new Error('Could not load the nuxt app')
+ error.body = window.document.body.innerHTML
+ throw error
+ }
+ // Used by nuxt.js to say when the components are loaded and the app ready
+ await new Promise((resolve) => {
+ window._onNuxtLoaded = () => resolve(window)
+ })
+ // Send back window object
+ return window
+ }
+}
+
+const parseTemplate = templateStr => _.template(templateStr, {
+ interpolate: /{{([\s\S]+?)}}/g
+})
+
+const resourceMap = [
+ {
+ key: 'clientManifest',
+ fileName: 'vue-ssr-client-manifest.json',
+ transform: JSON.parse
+ },
+ {
+ key: 'serverBundle',
+ fileName: 'server-bundle.json',
+ transform: JSON.parse
+ },
+ {
+ key: 'ssrTemplate',
+ fileName: 'index.ssr.html',
+ transform: parseTemplate
+ },
+ {
+ key: 'spaTemplate',
+ fileName: 'index.spa.html',
+ transform: parseTemplate
+ }
+]
+
+// Protector utility against request to SSR bundle files
+const ssrResourceRegex = new RegExp(resourceMap.map(resource => resource.fileName).join('|'), 'i')
diff --git a/lib/generate.js b/lib/generate.js
deleted file mode 100644
index 7525cb843d..0000000000
--- a/lib/generate.js
+++ /dev/null
@@ -1,161 +0,0 @@
-'use strict'
-
-import fs from 'fs-extra'
-import pify from 'pify'
-import _ from 'lodash'
-import { resolve, join, dirname, sep } from 'path'
-import { isUrl, promisifyRoute, waitFor } from './utils'
-import { minify } from 'html-minifier'
-const debug = require('debug')('nuxt:generate')
-const copy = pify(fs.copy)
-const remove = pify(fs.remove)
-const writeFile = pify(fs.writeFile)
-const mkdirp = pify(fs.mkdirp)
-
-const defaults = {
- dir: 'dist',
- routes: [],
- interval: 0,
- minify: {
- collapseBooleanAttributes: true,
- collapseWhitespace: true,
- decodeEntities: true,
- minifyCSS: true,
- minifyJS: true,
- processConditionalComments: true,
- removeAttributeQuotes: false,
- removeComments: false,
- removeEmptyAttributes: true,
- removeOptionalTags: true,
- removeRedundantAttributes: true,
- removeScriptTypeAttributes: false,
- removeStyleLinkTypeAttributes: false,
- removeTagWhitespace: false,
- sortAttributes: true,
- sortClassName: true,
- trimCustomFragments: true,
- useShortDoctype: true
- }
-}
-
-export default async function () {
- const s = Date.now()
- let errors = []
- /*
- ** Wait for modules to be initialized
- */
- await this.ready()
- /*
- ** Set variables
- */
- this.options.generate = _.defaultsDeep(this.options.generate, defaults)
- var srcStaticPath = resolve(this.srcDir, 'static')
- var srcBuiltPath = resolve(this.buildDir, 'dist')
- var distPath = resolve(this.dir, this.options.generate.dir)
- var distNuxtPath = join(distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath))
- /*
- ** Launch build process
- */
- await this.build()
- /*
- ** Clean destination folder
- */
- try {
- await remove(distPath)
- debug('Destination folder cleaned')
- } catch (e) {}
- /*
- ** Copy static and built files
- */
- if (fs.existsSync(srcStaticPath)) {
- await copy(srcStaticPath, distPath)
- }
- await copy(srcBuiltPath, distNuxtPath)
- debug('Static & build files copied')
- if (this.options.router.mode !== 'hash') {
- // Resolve config.generate.routes promises before generating the routes
- try {
- var generateRoutes = await promisifyRoute(this.options.generate.routes || [])
- } catch (e) {
- console.error('Could not resolve routes') // eslint-disable-line no-console
- console.error(e) // eslint-disable-line no-console
- process.exit(1)
- throw e // eslint-disable-line no-unreachable
- }
- }
- function decorateWithPayloads (routes) {
- let routeMap = {}
- // Fill routeMap for known routes
- routes.forEach((route) => {
- routeMap[route] = {
- route,
- payload: null
- }
- })
- // Fill routeMap with given generate.routes
- generateRoutes.forEach((route) => {
- // route is either a string or like {route : "/my_route/1"}
- const path = _.isString(route) ? route : route.route
- routeMap[path] = {
- route: path,
- payload: route.payload || null
- }
- })
- return _.values(routeMap)
- }
- /*
- ** Generate only index.html for router.mode = 'hash'
- */
- let routes = (this.options.router.mode === 'hash') ? ['/'] : this.routes
- routes = decorateWithPayloads(routes)
-
- while (routes.length) {
- let n = 0
- await Promise.all(routes.splice(0, 500).map(async ({route, payload}) => {
- await waitFor(n++ * this.options.generate.interval)
- let html
- try {
- const res = await this.renderRoute(route, { _generate: true, payload })
- html = res.html
- if (res.error) {
- errors.push({ type: 'handled', route, error: res.error })
- }
- } catch (err) {
- /* istanbul ignore next */
- return errors.push({ type: 'unhandled', route, error: err })
- }
- if (this.options.generate.minify) {
- try {
- html = minify(html, this.options.generate.minify)
- } catch (err) /* istanbul ignore next */ {
- const minifyErr = new Error(`HTML minification failed. Make sure the route generates valid HTML. Failed HTML:\n ${html}`)
- errors.push({ type: 'unhandled', route, error: minifyErr })
- }
- }
- let path = join(route, sep, 'index.html') // /about -> /about/index.html
- debug('Generate file: ' + path)
- path = join(distPath, path)
- // Make sure the sub folders are created
- await mkdirp(dirname(path))
- await writeFile(path, html, 'utf8')
- }))
- }
- // Add .nojekyll file to let Github Pages add the _nuxt/ folder
- // https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/
- const nojekyllPath = resolve(distPath, '.nojekyll')
- writeFile(nojekyllPath, '')
- const duration = Math.round((Date.now() - s) / 100) / 10
- debug(`HTML Files generated in ${duration}s`)
-
- if (errors.length) {
- const report = errors.map(({ type, route, error }) => {
- /* istanbul ignore if */
- if (type === 'unhandled') {
- return `Route: '${route}'\n${error.stack}`
- } else {
- return `Route: '${route}' thrown an error: \n` + JSON.stringify(error)
- }
- })
- console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console
- }
-}
diff --git a/lib/index.js b/lib/index.js
new file mode 100755
index 0000000000..aa7df41b13
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,4 @@
+import * as core from './core'
+import * as builder from './builder'
+
+export default Object.assign({}, core, builder)
diff --git a/lib/nuxt.js b/lib/nuxt.js
deleted file mode 100644
index a7373a8cc6..0000000000
--- a/lib/nuxt.js
+++ /dev/null
@@ -1,181 +0,0 @@
-'use strict'
-
-import _ from 'lodash'
-import compression from 'compression'
-import fs from 'fs-extra'
-import pify from 'pify'
-import Server from './server'
-import Module from './module'
-import * as build from './build'
-import * as render from './render'
-import generate from './generate'
-import serveStatic from 'serve-static'
-import { resolve, join } from 'path'
-import * as utils from './utils'
-
-class Nuxt {
- constructor (options = {}) {
- const defaults = {
- dev: (process.env.NODE_ENV !== 'production'),
- buildDir: '.nuxt',
- env: {},
- head: {
- meta: [],
- link: [],
- style: [],
- script: []
- },
- plugins: [],
- css: [],
- modules: [],
- layouts: {},
- serverMiddleware: [],
- ErrorPage: null,
- loading: {
- color: 'black',
- failedColor: 'red',
- height: '2px',
- duration: 5000
- },
- transition: {
- name: 'page',
- mode: 'out-in'
- },
- router: {
- mode: 'history',
- base: '/',
- middleware: [],
- linkActiveClass: 'nuxt-link-active',
- linkExactActiveClass: 'nuxt-link-exact-active',
- extendRoutes: null,
- scrollBehavior: null
- },
- render: {
- http2: {
- push: false
- },
- static: {},
- gzip: {
- threshold: 0
- },
- etag: {
- weak: true // Faster for responses > 5KB
- }
- },
- watchers: {
- webpack: {},
- chokidar: {}
- }
- }
- // Sanitization
- if (options.loading === true) delete options.loading
- if (options.router && typeof options.router.middleware === 'string') options.router.middleware = [options.router.middleware]
- if (options.router && typeof options.router.base === 'string') {
- this._routerBaseSpecified = true
- }
- if (typeof options.transition === 'string') options.transition = {name: options.transition}
- this.options = _.defaultsDeep(options, defaults)
- // Ready variable
- this._ready = false
- // Env variables
- this.dev = this.options.dev
- // Explicit srcDir, rootDir and buildDir
- this.dir = (typeof options.rootDir === 'string' && options.rootDir ? options.rootDir : process.cwd())
- this.srcDir = (typeof options.srcDir === 'string' && options.srcDir ? resolve(this.dir, options.srcDir) : this.dir)
- this.buildDir = join(this.dir, options.buildDir)
- options.rootDir = this.dir
- options.srcDir = this.srcDir
- options.buildDir = this.buildDir
- // If store defined, update store options to true
- if (fs.existsSync(join(this.srcDir, 'store'))) {
- this.options.store = true
- }
- // If app.html is defined, set the template path to the user template
- this.options.appTemplatePath = resolve(__dirname, 'views/app.template.html')
- if (fs.existsSync(join(this.srcDir, 'app.html'))) {
- this.options.appTemplatePath = join(this.srcDir, 'app.html')
- }
- // renderer used by Vue.js (via createBundleRenderer)
- this.renderer = null
- // For serving static/ files to /
- this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static'), this.options.render.static))
- // For serving .nuxt/dist/ files (only when build.publicPath is not an URL)
- this.serveStaticNuxt = pify(serveStatic(resolve(this.buildDir, 'dist'), {
- maxAge: (this.dev ? 0 : '1y') // 1 year in production
- }))
- // gzip middleware for production
- if (!this.dev && this.options.render.gzip) {
- this.gzipMiddleware = pify(compression(this.options.render.gzip))
- }
- // Add this.Server Class
- this.Server = Server
- // Add this.build
- build.options.call(this) // Add build options
- this.build = build.build.bind(this)
- // Error template
- this.errorTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
- interpolate: /{{([\s\S]+?)}}/g
- })
- // Add this.render and this.renderRoute
- this.render = render.render.bind(this)
- this.renderRoute = render.renderRoute.bind(this)
- this.renderAndGetWindow = render.renderAndGetWindow.bind(this)
- // Add this.generate
- this.generate = generate.bind(this)
- // Add this.utils (tests purpose)
- this.utils = utils
- // Add module integration
- this.module = new Module(this)
- // Init nuxt.js
- this._ready = this.ready()
- // Return nuxt.js instance
- return this
- }
-
- async ready () {
- if (this._ready) {
- await this._ready
- return this
- }
- // Init modules
- await this.module.ready()
- // Launch build in development but don't wait for it to be finished
- if (this.dev) {
- this.build()
- } else {
- build.production.call(this)
- }
- return this
- }
-
- close (callback) {
- let promises = []
- /* istanbul ignore if */
- if (this.webpackDevMiddleware) {
- const p = new Promise((resolve, reject) => {
- this.webpackDevMiddleware.close(() => resolve())
- })
- promises.push(p)
- }
- /* istanbul ignore if */
- if (this.webpackServerWatcher) {
- const p = new Promise((resolve, reject) => {
- this.webpackServerWatcher.close(() => resolve())
- })
- promises.push(p)
- }
- /* istanbul ignore if */
- if (this.filesWatcher) {
- this.filesWatcher.close()
- }
- /* istanbul ignore if */
- if (this.customFilesWatcher) {
- this.customFilesWatcher.close()
- }
- return Promise.all(promises).then(() => {
- if (typeof callback === 'function') callback()
- })
- }
-}
-
-export default Nuxt
diff --git a/lib/render.js b/lib/render.js
deleted file mode 100644
index 238c31a528..0000000000
--- a/lib/render.js
+++ /dev/null
@@ -1,196 +0,0 @@
-'use strict'
-
-import ansiHTML from 'ansi-html'
-import serialize from 'serialize-javascript'
-import generateETag from 'etag'
-import fresh from 'fresh'
-import { getContext, setAnsiColors, encodeHtml } from './utils'
-
-const debug = require('debug')('nuxt:render')
-// force blue color
-debug.color = 4
-setAnsiColors(ansiHTML)
-
-export async function render (req, res) {
- // Wait for nuxt.js to be ready
- await this.ready()
- // Check if project is built for production
- if (!this.renderer && !this.dev) {
- console.error('> No build files found, please run `nuxt build` before launching `nuxt start`') // eslint-disable-line no-console
- process.exit(1)
- }
- /* istanbul ignore if */
- if (!this.renderer || !this.appTemplate) {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(this.render(req, res))
- }, 1000)
- })
- }
- // Get context
- const context = getContext(req, res)
- res.statusCode = 200
- try {
- if (this.dev) {
- // Call webpack middleware only in development
- await this.webpackDevMiddleware(req, res)
- await this.webpackHotMiddleware(req, res)
- }
- if (!this.dev && this.options.render.gzip) {
- await this.gzipMiddleware(req, res)
- }
- // If base in req.url, remove it for the middleware and vue-router
- if (this.options.router.base !== '/' && req.url.indexOf(this.options.router.base) === 0) {
- // Compatibility with base url for dev server
- req.url = req.url.replace(this.options.router.base, '/')
- }
- // Serve static/ files
- await this.serveStatic(req, res)
- // Serve .nuxt/dist/ files (only for production)
- if (!this.dev && req.url.indexOf(this.options.build.publicPath) === 0) {
- const url = req.url
- req.url = req.url.replace(this.options.build.publicPath, '/')
- await this.serveStaticNuxt(req, res)
- /* istanbul ignore next */
- req.url = url
- }
- if (this.dev && req.url.indexOf(this.options.build.publicPath) === 0 && req.url.includes('.hot-update.json')) {
- res.statusCode = 404
- return res.end()
- }
- const {html, error, redirected, resourceHints} = await this.renderRoute(req.url, context)
- if (redirected) {
- return html
- }
- if (error) {
- res.statusCode = context.nuxt.error.statusCode || 500
- }
- // ETag header
- if (!error && this.options.render.etag) {
- const etag = generateETag(html, this.options.render.etag)
- if (fresh(req.headers, {etag})) {
- res.statusCode = 304
- res.end()
- return
- }
- res.setHeader('ETag', etag)
- }
- // HTTP2 push headers
- if (!error && this.options.render.http2.push) {
- // Parse resourceHints to extract HTTP.2 prefetch/push headers
- // https://w3c.github.io/preload/#server-push-http-2
- const regex = /link rel="([^"]*)" href="([^"]*)" as="([^"]*)"/g
- const pushAssets = []
- let m
- while (m = regex.exec(resourceHints)) { // eslint-disable-line no-cond-assign
- const [_, rel, href, as] = m // eslint-disable-line no-unused-vars
- if (rel === 'preload') {
- pushAssets.push(`<${href}>; rel=${rel}; as=${as}`)
- }
- }
- // Pass with single Link header
- // https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
- res.setHeader('Link', pushAssets.join(','))
- }
- res.setHeader('Content-Type', 'text/html; charset=utf-8')
- res.setHeader('Content-Length', Buffer.byteLength(html))
- res.end(html, 'utf8')
- return html
- } catch (err) {
- if (context.redirected) {
- console.error(err) // eslint-disable-line no-console
- return err
- }
- const html = this.errorTemplate({
- /* istanbul ignore if */
- error: err,
- stack: ansiHTML(encodeHtml(err.stack))
- })
- res.statusCode = 500
- res.setHeader('Content-Type', 'text/html; charset=utf-8')
- res.setHeader('Content-Length', Buffer.byteLength(html))
- res.end(html, 'utf8')
- return err
- }
-}
-
-export async function renderRoute (url, context = {}) {
- // Wait for modules to be initialized
- await this.ready()
- // Log rendered url
- debug(`Rendering url ${url}`)
- // Add url and isSever to the context
- context.url = url
- context.isServer = true
- // Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
- let APP = await this.renderToString(context)
- if (!context.nuxt.serverRendered) {
- APP = '
'
- }
- const m = context.meta.inject()
- let HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
- if (this._routerBaseSpecified) {
- HEAD += `
`
- }
- const resourceHints = context.renderResourceHints()
- HEAD += resourceHints + context.renderStyles()
- APP += ``
- APP += context.renderScripts()
- const html = this.appTemplate({
- HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
- BODY_ATTRS: m.bodyAttrs.text(),
- HEAD,
- APP
- })
- return {
- html,
- resourceHints,
- error: context.nuxt.error,
- redirected: context.redirected
- }
-}
-
-// Function used to do dom checking via jsdom
-let jsdom = null
-export async function renderAndGetWindow (url, opts = {}) {
- /* istanbul ignore if */
- if (!jsdom) {
- try {
- jsdom = require('jsdom')
- } catch (e) {
- console.error('Fail when calling nuxt.renderAndGetWindow(url)') // eslint-disable-line no-console
- console.error('jsdom module is not installed') // eslint-disable-line no-console
- console.error('Please install jsdom with: npm install --save-dev jsdom') // eslint-disable-line no-console
- process.exit(1)
- }
- }
- let options = {
- resources: 'usable', // load subresources (https://github.com/tmpvar/jsdom#loading-subresources)
- runScripts: 'dangerously',
- beforeParse (window) {
- // Mock window.scrollTo
- window.scrollTo = () => {
- }
- }
- }
- if (opts.virtualConsole !== false) {
- options.virtualConsole = new jsdom.VirtualConsole().sendTo(console)
- }
- url = url || 'http://localhost:3000'
- const {window} = await jsdom.JSDOM.fromURL(url, options)
- // If Nuxt could not be loaded (error from the server-side)
- const nuxtExists = window.document.body.innerHTML.includes('window.__NUXT__')
- if (!nuxtExists) {
- /* istanbul ignore next */
- let error = new Error('Could not load the nuxt app')
- /* istanbul ignore next */
- error.body = window.document.body.innerHTML
- throw error
- }
- // Used by nuxt.js to say when the components are loaded and the app ready
- await new Promise((resolve) => {
- window._onNuxtLoaded = () => resolve(window)
- })
- // Send back window object
- return window
-}
diff --git a/lib/server.js b/lib/server.js
deleted file mode 100644
index 63766e4563..0000000000
--- a/lib/server.js
+++ /dev/null
@@ -1,65 +0,0 @@
-'use strict'
-
-const http = require('http')
-const connect = require('connect')
-const path = require('path')
-
-class Server {
- constructor (nuxt) {
- this.nuxt = nuxt
- // Initialize
- this.app = connect()
- this.server = http.createServer(this.app)
- this.nuxt.ready()
- .then(() => {
- // Add Middleware
- this.nuxt.options.serverMiddleware.forEach(m => {
- this.useMiddleware(m)
- })
- // Add default render middleware
- this.useMiddleware(this.render.bind(this))
- })
- return this
- }
-
- useMiddleware (m) {
- // Require if needed
- if (typeof m === 'string') {
- let src = m
- // Using ~ or ./ shorthand to resolve from project srcDir
- if (src.indexOf('~') === 0 || src.indexOf('./') === 0) {
- src = path.join(this.nuxt.options.srcDir, src.substr(1))
- }
- // eslint-disable-next-line no-eval
- m = eval('require')(src)
- }
- if (m instanceof Function) {
- this.app.use(m)
- } else if (m && m.path && m.handler) {
- this.app.use(m.path, m.handler)
- }
- }
-
- render (req, res, next) {
- this.nuxt.render(req, res)
- return this
- }
-
- listen (port, host) {
- host = host || '127.0.0.1'
- port = port || 3000
- this.nuxt.ready()
- .then(() => {
- this.server.listen(port, host, () => {
- console.log('Ready on http://%s:%s', host, port) // eslint-disable-line no-console
- })
- })
- return this
- }
-
- close (cb) {
- return this.server.close(cb)
- }
-}
-
-export default Server
diff --git a/lib/utils.js b/lib/utils.js
deleted file mode 100644
index 4e69f0f636..0000000000
--- a/lib/utils.js
+++ /dev/null
@@ -1,99 +0,0 @@
-'use strict'
-import { resolve, sep } from 'path'
-import _ from 'lodash'
-
-export function encodeHtml (str) {
- return str.replace(//g, '>')
-}
-
-export function getContext (req, res) {
- return { req, res }
-}
-
-export function setAnsiColors (ansiHTML) {
- ansiHTML.setColors({
- reset: ['efefef', 'a6004c'],
- darkgrey: '5a012b',
- yellow: 'ffab07',
- green: 'aeefba',
- magenta: 'ff84bf',
- blue: '3505a0',
- cyan: '56eaec',
- red: '4e053a'
- })
-}
-
-export async function waitFor (ms) {
- return new Promise(function (resolve) {
- setTimeout(resolve, (ms || 0))
- })
-}
-
-export function urlJoin () {
- return [].slice.call(arguments).join('/').replace(/\/+/g, '/').replace(':/', '://')
-}
-
-export function isUrl (url) {
- return (url.indexOf('http') === 0 || url.indexOf('//') === 0)
-}
-
-export function promisifyRoute (fn) {
- // If routes is an array
- if (Array.isArray(fn)) {
- return Promise.resolve(fn)
- }
- // If routes is a function expecting a callback
- if (fn.length === 1) {
- return new Promise((resolve, reject) => {
- fn(function (err, routeParams) {
- if (err) {
- reject(err)
- }
- resolve(routeParams)
- })
- })
- }
- let promise = fn()
- if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) {
- promise = Promise.resolve(promise)
- }
- return promise
-}
-
-export function sequence (tasks, fn) {
- return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve())
-}
-
-export function chainFn (base, fn) {
- /* istanbul ignore if */
- if (!(fn instanceof Function)) {
- return
- }
- return function () {
- if (base instanceof Function) {
- base.apply(this, arguments)
- }
- fn.apply(this, arguments)
- }
-}
-
-export function wp (p) {
- /* istanbul ignore if */
- if (/^win/.test(process.platform)) {
- p = p.replace(/\\/g, '\\\\')
- }
- return p
-}
-
-const reqSep = /\//g
-const sysSep = _.escapeRegExp(sep)
-const normalize = string => string.replace(reqSep, sysSep)
-
-export function r () {
- let args = Array.from(arguments)
- if (_.last(args).includes('~')) {
- return wp(_.last(args))
- }
- args = args.map(normalize)
- return wp(resolve.apply(null, args))
-}
diff --git a/lib/views/error.html b/lib/views/error.html
deleted file mode 100644
index 497a0e28ed..0000000000
--- a/lib/views/error.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
Nuxt.js Error
-
-
-
Nuxt.js Error:
-
{{ stack }}
-
-
diff --git a/lib/webpack/base.config.js b/lib/webpack/base.config.js
deleted file mode 100644
index 406f198fb7..0000000000
--- a/lib/webpack/base.config.js
+++ /dev/null
@@ -1,95 +0,0 @@
-'use strict'
-
-import vueLoaderConfig from './vue-loader.config'
-import { defaults } from 'lodash'
-import { join } from 'path'
-import { isUrl, urlJoin } from '../utils'
-import { styleLoader, extractStyles } from './helpers'
-import ExtractTextPlugin from 'extract-text-webpack-plugin'
-
-/*
-|--------------------------------------------------------------------------
-| Webpack Shared Config
-|
-| This is the config which is extended by the server and client
-| webpack config files
-|--------------------------------------------------------------------------
-*/
-export default function ({ isClient, isServer }) {
- const nodeModulesDir = join(__dirname, '..', 'node_modules')
- let config = {
- devtool: (this.dev ? 'cheap-module-source-map' : false),
- entry: {
- vendor: ['vue', 'vue-router', 'vue-meta']
- },
- output: {
- publicPath: (isUrl(this.options.build.publicPath) ? this.options.build.publicPath : urlJoin(this.options.router.base, this.options.build.publicPath))
- },
- performance: {
- maxEntrypointSize: 300000,
- maxAssetSize: 300000,
- hints: (this.dev ? false : 'warning')
- },
- resolve: {
- extensions: ['.js', '.json', '.vue', '.ts'],
- // Disable for now
- alias: {
- '~': join(this.srcDir),
- 'static': join(this.srcDir, 'static'), // use in template with
- '~static': join(this.srcDir, 'static'),
- 'assets': join(this.srcDir, 'assets'), // use in template with
- '~assets': join(this.srcDir, 'assets'),
- '~plugins': join(this.srcDir, 'plugins'),
- '~store': join(this.buildDir, 'store'),
- '~router': join(this.buildDir, 'router'),
- '~pages': join(this.srcDir, 'pages'),
- '~components': join(this.srcDir, 'components')
- },
- modules: [
- join(this.dir, 'node_modules'),
- nodeModulesDir
- ]
- },
- resolveLoader: {
- modules: [
- join(this.dir, 'node_modules'),
- nodeModulesDir
- ]
- },
- module: {
- rules: [
- {
- test: /\.vue$/,
- loader: 'vue-loader',
- query: vueLoaderConfig.call(this, { isClient, isServer })
- },
- {
- test: /\.js$/,
- loader: 'babel-loader',
- exclude: /node_modules/,
- query: defaults(this.options.build.babel, {
- presets: ['vue-app'],
- babelrc: false,
- cacheDirectory: !!this.dev
- })
- },
- { test: /\.css$/, use: styleLoader.call(this, 'css') },
- { test: /\.less$/, use: styleLoader.call(this, 'less', 'less-loader') },
- { test: /\.sass$/, use: styleLoader.call(this, 'sass', 'sass-loader?indentedSyntax&sourceMap') },
- { test: /\.scss$/, use: styleLoader.call(this, 'sass', 'sass-loader?sourceMap') },
- { test: /\.styl(us)?$/, use: styleLoader.call(this, 'stylus', 'stylus-loader') }
- ]
- },
- plugins: this.options.build.plugins
- }
- // CSS extraction
- if (extractStyles.call(this)) {
- config.plugins.push(
- new ExtractTextPlugin({filename: this.options.build.filenames.css})
- )
- }
- // Add nuxt build loaders (can be configured in nuxt.config.js)
- config.module.rules = config.module.rules.concat(this.options.build.loaders)
- // Return config
- return config
-}
diff --git a/lib/webpack/client.config.js b/lib/webpack/client.config.js
deleted file mode 100644
index a874071ddf..0000000000
--- a/lib/webpack/client.config.js
+++ /dev/null
@@ -1,134 +0,0 @@
-'use strict'
-
-import { each, defaults } from 'lodash'
-import webpack from 'webpack'
-import VueSSRClientPlugin from 'vue-server-renderer/client-plugin'
-import HTMLPlugin from 'html-webpack-plugin'
-import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
-import ProgressBarPlugin from 'progress-bar-webpack-plugin'
-import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
-import OfflinePlugin from 'offline-plugin'
-import base from './base.config.js'
-import { resolve } from 'path'
-
-/*
-|--------------------------------------------------------------------------
-| Webpack Client Config
-|
-| Generate public/dist/client-vendor-bundle.js
-| Generate public/dist/client-bundle.js
-|
-| In production, will generate public/dist/style.css
-|--------------------------------------------------------------------------
-*/
-export default function () {
- let config = base.call(this, { isClient: true })
-
- // Entry
- config.entry.app = resolve(this.buildDir, 'client.js')
-
- // Add vendors
- if (this.options.store) {
- config.entry.vendor.push('vuex')
- }
- config.entry.vendor = config.entry.vendor.concat(this.options.build.vendor)
-
- // Output
- config.output.path = resolve(this.buildDir, 'dist')
- config.output.filename = this.options.build.filenames.app
-
- // env object defined in nuxt.config.js
- let env = {}
- each(this.options.env, (value, key) => {
- env['process.env.' + key] = (typeof value === 'string' ? JSON.stringify(value) : value)
- })
- // Webpack plugins
- config.plugins = (config.plugins || []).concat([
- // Strip comments in Vue code
- new webpack.DefinePlugin(Object.assign(env, {
- 'process.env.NODE_ENV': JSON.stringify(env.NODE_ENV || (this.dev ? 'development' : 'production')),
- 'process.BROWSER_BUILD': true,
- 'process.SERVER_BUILD': false,
- 'process.browser': true,
- 'process.server': true
- })),
- // Extract vendor chunks for better caching
- new webpack.optimize.CommonsChunkPlugin({
- name: 'vendor',
- filename: this.options.build.filenames.vendor,
- minChunks (module) {
- // A module is extracted into the vendor chunk when...
- return (
- // If it's inside node_modules
- /node_modules/.test(module.context) &&
- // Do not externalize if the request is a CSS file
- !/\.(css|less|scss|sass|styl|stylus)$/.test(module.request)
- )
- }
- }),
- // Extract webpack runtime & manifest
- new webpack.optimize.CommonsChunkPlugin({
- name: 'manifest',
- minChunks: Infinity,
- filename: this.options.build.filenames.manifest
- }),
- // Generate output HTML
- new HTMLPlugin({
- template: this.options.appTemplatePath,
- inject: false // <- Resources will be injected using vue server renderer
- }),
- // Generate client manifest json
- new VueSSRClientPlugin({
- filename: 'client-manifest.json'
- })
- ])
- // client bundle progress bar
- config.plugins.push(
- new ProgressBarPlugin()
- )
- // Add friendly error plugin
- if (this.dev) {
- config.plugins.push(new FriendlyErrorsWebpackPlugin())
- }
- // Production client build
- if (!this.dev) {
- config.plugins.push(
- // This is needed in webpack 2 for minifying CSS
- new webpack.LoaderOptionsPlugin({
- minimize: true
- }),
- // Minify JS
- new webpack.optimize.UglifyJsPlugin({
- sourceMap: true,
- compress: {
- warnings: false
- }
- })
- )
- }
- // Extend config
- if (typeof this.options.build.extend === 'function') {
- this.options.build.extend.call(this, config, {
- dev: this.dev,
- isClient: true
- })
- }
- // Offline-plugin integration
- if (!this.dev && this.options.offline) {
- const offlineOpts = typeof this.options.offline === 'object' ? this.options.offline : {}
- config.plugins.push(
- new OfflinePlugin(defaults(offlineOpts, {}))
- )
- }
- // Webpack Bundle Analyzer
- if (!this.dev && this.options.build.analyze) {
- let options = {}
- if (typeof this.options.build.analyze === 'object') {
- options = this.options.build.analyze
- }
- config.plugins.push(
- new BundleAnalyzerPlugin(options)
- )
- }
- return config
-}
diff --git a/lib/webpack/helpers.js b/lib/webpack/helpers.js
deleted file mode 100755
index e3fae8288e..0000000000
--- a/lib/webpack/helpers.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import ExtractTextPlugin from 'extract-text-webpack-plugin'
-
-export function extractStyles () {
- return !this.dev && this.options.build.extractCSS
-}
-
-export function styleLoader (ext, loader = []) {
- if (extractStyles.call(this)) {
- return ExtractTextPlugin.extract({
- use: ['css-loader?minify&sourceMap'].concat(loader),
- fallback: 'vue-style-loader?sourceMap'
- })
- }
- return ['vue-style-loader?sourceMap', 'css-loader?sourceMap'].concat(loader)
-}
diff --git a/lib/webpack/server.config.js b/lib/webpack/server.config.js
deleted file mode 100644
index b58b80dcfe..0000000000
--- a/lib/webpack/server.config.js
+++ /dev/null
@@ -1,72 +0,0 @@
-'use strict'
-
-import webpack from 'webpack'
-import VueSSRServerPlugin from 'vue-server-renderer/server-plugin'
-import nodeExternals from 'webpack-node-externals'
-import base from './base.config.js'
-import { each } from 'lodash'
-import { resolve } from 'path'
-
-/*
-|--------------------------------------------------------------------------
-| Webpack Server Config
-|--------------------------------------------------------------------------
-*/
-export default function () {
- let config = base.call(this, { isServer: true })
-
- // env object defined in nuxt.config.js
- let env = {}
- each(this.options.env, (value, key) => {
- env['process.env.' + key] = (typeof value === 'string' ? JSON.stringify(value) : value)
- })
-
- config = Object.assign(config, {
- target: 'node',
- devtool: (this.dev ? 'source-map' : false),
- entry: resolve(this.buildDir, 'server.js'),
- output: Object.assign({}, config.output, {
- path: resolve(this.buildDir, 'dist'),
- filename: 'server-bundle.js',
- libraryTarget: 'commonjs2'
- }),
- performance: {
- hints: false
- },
- externals: [
- nodeExternals({
- // load non-javascript files with extensions, presumably via loaders
- whitelist: [/\.(?!(?:js|json)$).{1,5}$/i]
- })
- ],
- plugins: (config.plugins || []).concat([
- new VueSSRServerPlugin({
- filename: 'server-bundle.json'
- }),
- new webpack.DefinePlugin(Object.assign(env, {
- 'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
- 'process.BROWSER_BUILD': false, // deprecated
- 'process.SERVER_BUILD': true, // deprecated
- 'process.browser': false,
- 'process.server': true
- }))
- ])
- })
- // This is needed in webpack 2 for minifying CSS
- if (!this.dev) {
- config.plugins.push(
- new webpack.LoaderOptionsPlugin({
- minimize: true
- })
- )
- }
-
- // Extend config
- if (typeof this.options.build.extend === 'function') {
- this.options.build.extend(config, {
- dev: this.dev,
- isServer: true
- })
- }
- return config
-}
diff --git a/lib/webpack/vue-loader.config.js b/lib/webpack/vue-loader.config.js
deleted file mode 100644
index ae4c7b8aba..0000000000
--- a/lib/webpack/vue-loader.config.js
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict'
-
-import { defaults } from 'lodash'
-import { extractStyles, styleLoader } from './helpers'
-
-export default function ({ isClient }) {
- let babelOptions = JSON.stringify(defaults(this.options.build.babel, {
- presets: ['vue-app'],
- babelrc: false,
- cacheDirectory: !!this.dev
- }))
-
- // https://github.com/vuejs/vue-loader/blob/master/docs/en/configurations
- let config = {
- postcss: this.options.build.postcss,
- loaders: {
- 'js': 'babel-loader?' + babelOptions,
- 'css': styleLoader.call(this, 'css'),
- 'less': styleLoader.call(this, 'less', 'less-loader'),
- 'sass': styleLoader.call(this, 'sass', 'sass-loader?indentedSyntax&?sourceMap'),
- 'scss': styleLoader.call(this, 'sass', 'sass-loader?sourceMap'),
- 'stylus': styleLoader.call(this, 'stylus', 'stylus-loader'),
- 'styl': styleLoader.call(this, 'stylus', 'stylus-loader')
- },
- preserveWhitespace: false,
- extractCSS: extractStyles.call(this)
- }
- // Return the config
- return config
-}
diff --git a/package.json b/package.json
index 1c540b6ce2..587da9634f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nuxt",
- "version": "1.0.0-alpha.4",
+ "version": "1.0.0-rc11",
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
"contributors": [
{
@@ -14,7 +14,6 @@
}
],
"main": "./index.js",
- "types": "./index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
@@ -23,7 +22,7 @@
"files": [
"bin",
"dist",
- "index.d.ts",
+ "lib",
"index.js"
],
"keywords": [
@@ -42,91 +41,122 @@
"bin": {
"nuxt": "./bin/nuxt"
},
+ "nyc": {
+ "include": [
+ "lib"
+ ]
+ },
"scripts": {
- "test": "npm run lint && nyc ava --verbose --serial test/",
+ "test": "npm run lint && cross-env NODE_ENV=test npm run build:nuxt && nyc ava --verbose --serial test/ -- && nyc report --reporter=html",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
- "lint": "eslint --ext .js,.vue bin lib pages test/*.js --ignore-pattern lib/app",
- "build": "webpack",
- "watch": "webpack --watch",
+ "lint": "eslint --ext .js,.vue bin/ lib/ test/ examples/",
+ "build": "rimraf dist/ && npm run build:nuxt && npm run build:core",
+ "build:nuxt": "rollup -c build/rollup.config.js --environment TARGET:nuxt",
+ "build:core": "rollup -c build/rollup.config.js --environment TARGET:core",
+ "watch": "npm run build:nuxt -- -w",
+ "make-start": "node ./build/start.js",
"precommit": "npm run lint",
- "prepublish": "npm run build",
- "postinstall": "opencollective postinstall"
+ "prepare": "npm run build && npm run make-start",
+ "postinstall": "opencollective postinstall || exit 0"
},
"engines": {
- "node": ">=4.3.0 <5.0.0 || >=5.10",
- "npm": ">=3.0.0"
+ "node": ">=6.11",
+ "npm": ">=3.10.0"
},
"dependencies": {
+ "@nuxtjs/youch": "^3.1.0",
"ansi-html": "^0.0.7",
- "autoprefixer": "^7.1.1",
- "babel-core": "^6.24.1",
- "babel-loader": "^7.0.0",
- "babel-preset-es2015": "^6.24.1",
- "babel-preset-vue-app": "^1.2.0",
+ "autoprefixer": "^7.1.6",
+ "babel-core": "^6.26.0",
+ "babel-loader": "^7.1.2",
+ "babel-preset-vue-app": "^1.3.1",
+ "caniuse-lite": "^1.0.30000758",
+ "chalk": "^2.3.0",
"chokidar": "^1.7.0",
- "compression": "^1.6.2",
- "connect": "^3.6.2",
- "css-loader": "^0.28.4",
- "debug": "^2.6.8",
- "etag": "^1.8.0",
- "extract-text-webpack-plugin": "^2.1.0",
- "file-loader": "^0.11.2",
- "fresh": "^0.5.0",
+ "clone": "^2.1.1",
+ "compression": "^1.7.1",
+ "connect": "^3.6.5",
+ "css-loader": "^0.28.7",
+ "debug": "^3.1.0",
+ "es6-promise": "^4.1.1",
+ "etag": "^1.8.1",
+ "extract-text-webpack-plugin": "^3.0.2",
+ "file-loader": "^1.1.5",
+ "fresh": "^0.5.2",
"friendly-errors-webpack-plugin": "^1.6.1",
- "fs-extra": "^3.0.1",
+ "fs-extra": "^4.0.2",
"glob": "^7.1.2",
"hash-sum": "^1.0.2",
- "html-minifier": "^3.5.2",
- "html-webpack-plugin": "^2.28.0",
+ "html-minifier": "^3.5.6",
+ "html-webpack-plugin": "^2.30.1",
"lodash": "^4.17.4",
+ "lru-cache": "^4.1.1",
"memory-fs": "^0.4.1",
- "offline-plugin": "^4.8.1",
+ "minimist": "^1.2.0",
+ "open-in-editor": "^2.2.0",
"opencollective": "^1.0.3",
"pify": "^3.0.0",
- "post-compile-webpack-plugin": "^0.1.1",
- "preload-webpack-plugin": "^1.2.2",
- "progress-bar-webpack-plugin": "^1.9.3",
- "script-ext-html-webpack-plugin": "^1.8.1",
- "serialize-javascript": "^1.3.0",
- "serve-static": "^1.12.3",
- "url-loader": "^0.5.8",
- "vue": "~2.3.3",
- "vue-loader": "^12.2.1",
- "vue-meta": "^1.0.4",
- "vue-router": "^2.5.3",
- "vue-server-renderer": "~2.3.3",
- "vue-ssr-html-stream": "^2.2.0",
- "vue-template-compiler": "~2.3.3",
- "vuex": "^2.3.1",
- "webpack": "^2.6.1",
- "webpack-bundle-analyzer": "^2.8.2",
- "webpack-dev-middleware": "^1.10.2",
- "webpack-hot-middleware": "^2.18.0",
+ "postcss": "^6.0.14",
+ "postcss-cssnext": "^3.0.2",
+ "postcss-import": "^11.0.0",
+ "postcss-loader": "^2.0.8",
+ "postcss-url": "^7.2.1",
+ "pretty-error": "^2.1.1",
+ "progress-bar-webpack-plugin": "^1.10.0",
+ "serialize-javascript": "^1.4.0",
+ "serve-static": "^1.13.1",
+ "server-destroy": "^1.0.1",
+ "source-map": "^0.6.1",
+ "source-map-support": "^0.5.0",
+ "uglifyjs-webpack-plugin": "^1.0.1",
+ "url-loader": "^0.6.2",
+ "vue": "^2.5.6",
+ "vue-loader": "^13.5.0",
+ "vue-meta": "^1.3.1",
+ "vue-router": "^3.0.1",
+ "vue-server-renderer": "^2.5.6",
+ "vue-template-compiler": "^2.5.6",
+ "vuex": "^3.0.1",
+ "webpack": "^3.8.1",
+ "webpack-bundle-analyzer": "^2.9.0",
+ "webpack-dev-middleware": "^1.12.0",
+ "webpack-hot-middleware": "^2.20.0",
"webpack-node-externals": "^1.6.0"
},
"devDependencies": {
- "ava": "^0.19.1",
- "babel-eslint": "^7.2.3",
+ "ava": "^0.23.0",
+ "babel-eslint": "^8.0.1",
"babel-plugin-array-includes": "^2.0.3",
- "babel-plugin-transform-async-to-generator": "^6.24.1",
- "babel-plugin-transform-runtime": "^6.23.0",
- "babel-preset-stage-2": "^6.24.1",
- "codecov": "^2.2.0",
- "copy-webpack-plugin": "^4.0.1",
- "eslint": "^3.19.0",
+ "babel-plugin-external-helpers": "^6.22.0",
+ "babel-plugin-istanbul": "^4.1.5",
+ "codecov": "^3.0.0",
+ "copy-webpack-plugin": "^4.2.0",
+ "cross-env": "^5.1.1",
+ "eslint": "^4.10.0",
"eslint-config-standard": "^10.2.1",
- "eslint-plugin-html": "^2.0.3",
- "eslint-plugin-import": "^2.3.0",
- "eslint-plugin-node": "^5.0.0",
- "eslint-plugin-promise": "^3.5.0",
+ "eslint-plugin-html": "^3.2.2",
+ "eslint-plugin-import": "^2.8.0",
+ "eslint-plugin-node": "^5.2.1",
+ "eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
- "finalhandler": "^1.0.3",
- "jsdom": "^11.0.0",
- "json-loader": "^0.5.4",
- "nyc": "^11.0.2",
- "request": "^2.81.0",
- "request-promise-native": "^1.0.4",
- "std-mocks": "^1.0.1"
+ "express": "^4.16.2",
+ "finalhandler": "^1.1.0",
+ "jsdom": "^11.3.0",
+ "json-loader": "^0.5.7",
+ "nyc": "^11.3.0",
+ "puppeteer": "^0.13.0",
+ "request": "^2.83.0",
+ "request-promise-native": "^1.0.5",
+ "rimraf": "^2.6.2",
+ "rollup": "^0.51.7",
+ "rollup-plugin-alias": "^1.4.0",
+ "rollup-plugin-babel": "^3.0.2",
+ "rollup-plugin-commonjs": "^8.2.6",
+ "rollup-plugin-node-resolve": "^3.0.0",
+ "rollup-plugin-replace": "^2.0.0",
+ "rollup-watch": "^4.3.1",
+ "std-mocks": "^1.0.1",
+ "uglify-js": "^3.1.7"
},
"collective": {
"type": "opencollective",
diff --git a/start/.gitignore b/start/.gitignore
new file mode 100644
index 0000000000..f0fd204587
--- /dev/null
+++ b/start/.gitignore
@@ -0,0 +1,3 @@
+*
+!README.md
+!package.json
\ No newline at end of file
diff --git a/start/README.md b/start/README.md
new file mode 100644
index 0000000000..731052c4e4
--- /dev/null
+++ b/start/README.md
@@ -0,0 +1,42 @@
+# nuxt-start
+
+> Start Nuxt.js Application in production mode.
+
+## Installation
+
+```bash
+npm install --save nuxt-start
+````
+
+Add/Update your "start" script into your `package.json`:
+
+```json
+{
+ "scripts": {
+ "start": "nuxt-start"
+ }
+}
+```
+
+## Usage
+
+```bash
+nuxt-start
-p -H -c
+```
+
+## Programmatic Usage
+
+```js
+const { Nuxt } = require('nuxt-start')
+
+// Require nuxt config
+const config = require('./nuxt.config.js')
+
+// Create a new nuxt instance
+const nuxt = new Nuxt(config)
+
+// Start nuxt.js server
+nuxt.listen(3000) // nuxt.listen(port, host)
+
+// Or use `nuxt.render` as an express middleware
+```
diff --git a/start/index.js b/start/index.js
new file mode 100644
index 0000000000..673042912a
--- /dev/null
+++ b/start/index.js
@@ -0,0 +1,16 @@
+/*!
+ * Nuxt.js
+ * (c) 2016-2017 Chopin Brothers
+ * Core maintainer: Pooya (@pi0)
+ * Released under the MIT License.
+ */
+
+// Node Source Map Support
+// https://github.com/evanw/node-source-map-support
+require('source-map-support').install()
+
+// Fix babel flag
+/* istanbul ignore else */
+process.noDeprecation = true
+
+module.exports = require('./dist/core')
diff --git a/start/package.json b/start/package.json
new file mode 100644
index 0000000000..c37586d9ff
--- /dev/null
+++ b/start/package.json
@@ -0,0 +1,78 @@
+{
+ "name": "nuxt-start",
+ "version": "1.0.0-rc11",
+ "description": "runtime-only build for nuxt",
+ "contributors": [
+ {
+ "name": "Sebastien Chopin (@Atinux)"
+ },
+ {
+ "name": "Alexandre Chopin (@alexchopin)"
+ },
+ {
+ "name": "Pooya Parsa (@pi0)"
+ }
+ ],
+ "main": "./index.js",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/nuxt/nuxt.js"
+ },
+ "files": [
+ "bin",
+ "dist",
+ "lib",
+ "index.js"
+ ],
+ "keywords": [
+ "nuxt",
+ "nuxt.js",
+ "nuxtjs",
+ "vue",
+ "vue.js",
+ "vuejs",
+ "vue universal",
+ "vue ssr",
+ "vue isomorphic",
+ "vue versatile"
+ ],
+ "homepage": "https://github.com/nuxt/nuxt.js#readme",
+ "bin": {
+ "nuxt-start": "./bin/nuxt-start"
+ },
+ "engines": {
+ "node": ">=6.11",
+ "npm": ">=3.10.0"
+ },
+ "dependencies": {
+ "source-map-support": "^0.5.0",
+ "pretty-error": "^2.1.1",
+ "minimist": "^1.2.0",
+ "lodash": "^4.17.4",
+ "debug": "^3.1.0",
+ "hash-sum": "^1.0.2",
+ "chalk": "^2.3.0",
+ "ansi-html": "^0.0.7",
+ "serialize-javascript": "^1.4.0",
+ "etag": "^1.8.1",
+ "fresh": "^0.5.2",
+ "serve-static": "^1.13.1",
+ "compression": "^1.7.1",
+ "fs-extra": "^4.0.2",
+ "vue-server-renderer": "^2.5.6",
+ "@nuxtjs/youch": "^3.1.0",
+ "source-map": "^0.6.1",
+ "connect": "^3.6.5",
+ "vue": "^2.5.6",
+ "vue-meta": "^1.3.1",
+ "lru-cache": "^4.1.1",
+ "server-destroy": "^1.0.1",
+ "open-in-editor": "^2.2.0"
+ },
+ "collective": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nuxtjs",
+ "logo": "https://opencollective.com/nuxtjs/logo.txt?reverse=true&variant=variant2"
+ }
+}
\ No newline at end of file
diff --git a/test/basic.csr.test.js b/test/basic.csr.test.js
new file mode 100644
index 0000000000..da09e77a1b
--- /dev/null
+++ b/test/basic.csr.test.js
@@ -0,0 +1,180 @@
+import test from 'ava'
+import { resolve } from 'path'
+import { Nuxt, Builder } from '../index'
+import * as browser from './helpers/browser'
+
+const port = 4003
+const url = (route) => 'http://localhost:' + port + route
+
+let nuxt = null
+let page = null
+
+// Init nuxt.js and create server listening on localhost:4003
+test.before('Init Nuxt.js', async t => {
+ const options = {
+ rootDir: resolve(__dirname, 'fixtures/basic'),
+ dev: false,
+ head: {
+ titleTemplate(titleChunk) {
+ return titleChunk ? `${titleChunk} - Nuxt.js` : 'Nuxt.js'
+ }
+ }
+ }
+ nuxt = new Nuxt(options)
+ await new Builder(nuxt).build()
+
+ await nuxt.listen(port, 'localhost')
+})
+
+test.before('Start browser', async t => {
+ await browser.start({
+ // slowMo: 50,
+ // headless: false
+ })
+})
+
+test('Open /', async t => {
+ page = await browser.page(url('/'))
+
+ t.is(await page.$text('h1'), 'Index page')
+})
+
+test('/stateless', async t => {
+ const { hook } = await page.nuxt.navigate('/stateless', false)
+ const loading = await page.nuxt.loadingData()
+
+ t.is(loading.show, true)
+ await hook
+ t.is(await page.$text('h1'), 'My component!')
+})
+
+test('/css', async t => {
+ await page.nuxt.navigate('/css')
+
+ t.is(await page.$text('.red'), 'This is red')
+ t.is(await page.$eval('.red', (red) => window.getComputedStyle(red).color), 'rgb(255, 0, 0)')
+})
+
+test('/stateful', async t => {
+ await page.nuxt.navigate('/stateful')
+
+ t.is(await page.$text('p'), 'The answer is 42')
+})
+
+test('/store', async t => {
+ await page.nuxt.navigate('/store')
+
+ t.is(await page.$text('h1'), 'Vuex Nested Modules')
+ t.is(await page.$text('p'), '1')
+})
+
+test('/head', async t => {
+ const msg = new Promise((resolve) => page.on('console', (msg) => resolve(msg.text)))
+ await page.nuxt.navigate('/head')
+ const metas = await page.$$attr('meta', 'content')
+
+ t.is(await msg, 'Body script!')
+ t.is(await page.title(), 'My title - Nuxt.js')
+ t.is(await page.$text('h1'), 'I can haz meta tags')
+ t.is(metas[0], 'my meta')
+})
+
+test('/async-data', async t => {
+ await page.nuxt.navigate('/async-data')
+
+ t.is(await page.$text('p'), 'Nuxt.js')
+})
+
+test('/await-async-data', async t => {
+ await page.nuxt.navigate('/await-async-data')
+
+ t.is(await page.$text('p'), 'Await Nuxt.js')
+})
+
+test('/callback-async-data', async t => {
+ await page.nuxt.navigate('/callback-async-data')
+
+ t.is(await page.$text('p'), 'Callback Nuxt.js')
+})
+
+test('/users/1', async t => {
+ await page.nuxt.navigate('/users/1')
+
+ t.is(await page.$text('h1'), 'User: 1')
+})
+
+test('/validate should display a 404', async t => {
+ await page.nuxt.navigate('/validate')
+ const error = await page.nuxt.errorData()
+
+ t.is(error.statusCode, 404)
+ t.is(error.message, 'This page could not be found')
+})
+
+test('/validate?valid=true', async t => {
+ await page.nuxt.navigate('/validate?valid=true')
+
+ t.is(await page.$text('h1'), 'I am valid')
+})
+
+test('/redirect', async t => {
+ await page.nuxt.navigate('/redirect')
+
+ t.is(await page.$text('h1'), 'Index page')
+})
+
+test('/error', async t => {
+ await page.nuxt.navigate('/error')
+
+ t.deepEqual(await page.nuxt.errorData(), { statusCode: 500 })
+ t.is(await page.$text('.title'), 'Error mouahahah')
+})
+
+test('/error2', async t => {
+ await page.nuxt.navigate('/error2')
+
+ t.is(await page.$text('.title'), 'Custom error')
+ t.deepEqual(await page.nuxt.errorData(), { message: 'Custom error' })
+})
+
+test('/redirect2', async t => {
+ await page.nuxt.navigate('/redirect2')
+
+ t.is(await page.$text('h1'), 'Index page')
+})
+
+test('/no-ssr', async t => {
+ await page.nuxt.navigate('/no-ssr')
+
+ t.is(await page.$text('h1'), 'Displayed only on client-side')
+})
+
+test('/meta', async t => {
+ await page.nuxt.navigate('/meta')
+
+ const state = await page.nuxt.storeState()
+ t.deepEqual(state.meta, [{ works: true }])
+})
+
+test('/fn-midd', async t => {
+ await page.nuxt.navigate('/fn-midd')
+
+ t.is(await page.$text('.title'), 'You need to ask the permission')
+ t.deepEqual(await page.nuxt.errorData(), { message: 'You need to ask the permission', statusCode: 403 })
+})
+
+test('/fn-midd?please=true', async t => {
+ await page.nuxt.navigate('/fn-midd?please=true')
+
+ const h1 = await page.$text('h1')
+ t.true(h1.includes('Date:'))
+})
+
+// Close server and ask nuxt to stop listening to file changes
+test.after('Closing server and nuxt.js', t => {
+ nuxt.close()
+})
+
+test.after('Stop browser', async t => {
+ await browser.stop()
+})
diff --git a/test/basic.dev.test.js b/test/basic.dev.test.js
index 42e60e22fa..a415ed4322 100644
--- a/test/basic.dev.test.js
+++ b/test/basic.dev.test.js
@@ -1,23 +1,23 @@
import test from 'ava'
import { resolve } from 'path'
-import rp from 'request-promise-native'
+// import rp from 'request-promise-native'
+import { Nuxt, Builder } from '../index.js'
+
const port = 4001
const url = (route) => 'http://localhost:' + port + route
let nuxt = null
-let server = null
// Init nuxt.js and create server listening on localhost:4000
test.before('Init Nuxt.js', async t => {
- const Nuxt = require('../')
const options = {
rootDir: resolve(__dirname, 'fixtures/basic'),
dev: true
}
nuxt = new Nuxt(options)
- await nuxt.build()
- server = new nuxt.Server(nuxt)
- server.listen(port, 'localhost')
+ await new Builder(nuxt).build()
+
+ await nuxt.listen(port, 'localhost')
})
test('/stateless', async t => {
@@ -26,17 +26,16 @@ test('/stateless', async t => {
t.true(html.includes('My component! '))
})
-test('/_nuxt/test.hot-update.json should returns empty html', async t => {
- try {
- await rp(url('/_nuxt/test.hot-update.json'))
- } catch (err) {
- t.is(err.statusCode, 404)
- t.is(err.response.body, '')
- }
-})
+// test('/_nuxt/test.hot-update.json should returns empty html', async t => {
+// try {
+// await rp(url('/_nuxt/test.hot-update.json'))
+// } catch (err) {
+// t.is(err.statusCode, 404)
+// t.is(err.response.body, '')
+// }
+// })
// Close server and ask nuxt to stop listening to file changes
-test.after('Closing server and nuxt.js', t => {
- server.close()
- nuxt.close(() => {})
+test.after('Closing server and nuxt.js', async t => {
+ await nuxt.close()
})
diff --git a/test/basic.fail.generate.test.js b/test/basic.fail.generate.test.js
index c1c85a30fa..e5386e0bcf 100644
--- a/test/basic.fail.generate.test.js
+++ b/test/basic.fail.generate.test.js
@@ -1,35 +1,22 @@
import test from 'ava'
import { resolve } from 'path'
+import { Nuxt, Builder, Generator } from '../index.js'
test('Fail with routes() which throw an error', async t => {
- const Nuxt = require('../')
const options = {
rootDir: resolve(__dirname, 'fixtures/basic'),
dev: false,
generate: {
- routes: function () {
- return new Promise((resolve, reject) => {
- reject(new Error('Not today!'))
- })
+ async routes() {
+ throw new Error('Not today!')
}
}
}
const nuxt = new Nuxt(options)
- return new Promise((resolve) => {
- var oldExit = process.exit
- var oldCE = console.error // eslint-disable-line no-console
- var _log = ''
- console.error = (s) => { _log += s } // eslint-disable-line no-console
- process.exit = (code) => {
- process.exit = oldExit
- console.error = oldCE // eslint-disable-line no-console
- t.is(code, 1)
- t.true(_log.includes('Could not resolve routes'))
- resolve()
- }
- nuxt.generate()
+ const builder = new Builder(nuxt)
+ const generator = new Generator(nuxt, builder)
+ return generator.generate()
.catch((e) => {
t.true(e.message === 'Not today!')
})
- })
})
diff --git a/test/basic.generate.nosubfolders.test.js b/test/basic.generate.nosubfolders.test.js
new file mode 100644
index 0000000000..ce9c1d813a
--- /dev/null
+++ b/test/basic.generate.nosubfolders.test.js
@@ -0,0 +1,138 @@
+import test from 'ava'
+import { resolve } from 'path'
+import { existsSync } from 'fs'
+import http from 'http'
+import serveStatic from 'serve-static'
+import finalhandler from 'finalhandler'
+import rp from 'request-promise-native'
+import { Nuxt, Builder, Generator } from '../index.js'
+
+const port = 4002
+const url = (route) => 'http://localhost:' + port + route
+
+let nuxt = null
+let server = null
+
+// Init nuxt.js and create server listening on localhost:4000
+test.before('Init Nuxt.js', async t => {
+ const rootDir = resolve(__dirname, 'fixtures/basic')
+ let config = require(resolve(rootDir, 'nuxt.config.js'))
+ config.rootDir = rootDir
+ config.dev = false
+ config.generate.subFolders = false
+
+ nuxt = new Nuxt(config)
+ const builder = new Builder(nuxt)
+ const generator = new Generator(nuxt, builder)
+ try {
+ await generator.generate() // throw an error (of /validate route)
+ } catch (err) {
+ }
+
+ const serve = serveStatic(resolve(__dirname, 'fixtures/basic/dist'), { extensions: ['html'] })
+ server = http.createServer((req, res) => {
+ serve(req, res, finalhandler(req, res))
+ })
+ server.listen(port)
+})
+
+test('Check ready hook called', async t => {
+ t.true(nuxt.__hook_called__)
+})
+
+test('/stateless', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/stateless'))
+ const html = window.document.body.innerHTML
+ t.true(html.includes('My component! '))
+})
+
+test('/css', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/css'))
+ const element = window.document.querySelector('.red')
+ t.not(element, null)
+ t.is(element.textContent, 'This is red')
+ t.is(element.className, 'red')
+ t.is(window.getComputedStyle(element).color, 'red')
+})
+
+test('/stateful', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/stateful'))
+ const html = window.document.body.innerHTML
+ t.true(html.includes(''))
+})
+
+test('/head', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/head'))
+ const html = window.document.body.innerHTML
+ const metas = window.document.getElementsByTagName('meta')
+ t.is(window.document.title, 'My title')
+ t.is(metas[0].getAttribute('content'), 'my meta')
+ t.true(html.includes('
I can haz meta tags '))
+})
+
+test('/async-data', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/async-data'))
+ const html = window.document.body.innerHTML
+ t.true(html.includes('Nuxt.js
'))
+})
+
+test('/users/1', async t => {
+ const html = await rp(url('/users/1'))
+ t.true(html.includes('User: 1 '))
+ t.true(existsSync(resolve(__dirname, 'fixtures/basic/dist', 'users/1.html')))
+ t.false(existsSync(resolve(__dirname, 'fixtures/basic/dist', 'users/1/index.html')))
+})
+
+test('/users/2', async t => {
+ const html = await rp(url('/users/2'))
+ t.true(html.includes('User: 2 '))
+})
+
+test('/users/3 (payload given)', async t => {
+ const html = await rp(url('/users/3'))
+ t.true(html.includes('User: 3000 '))
+})
+
+test('/users/4 -> Not found', async t => {
+ try {
+ await rp(url('/users/4'))
+ } catch (error) {
+ t.true(error.statusCode === 404)
+ t.true(error.response.body.includes('Cannot GET /users/4'))
+ }
+})
+
+test('/validate should not be server-rendered', async t => {
+ const html = await rp(url('/validate'))
+ t.true(html.includes('
'))
+ t.true(html.includes('serverRendered:!1'))
+})
+
+test('/validate -> should display a 404', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/validate'))
+ const html = window.document.body.innerHTML
+ t.true(html.includes('This page could not be found'))
+})
+
+test('/validate?valid=true', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/validate?valid=true'))
+ const html = window.document.body.innerHTML
+ t.true(html.includes('I am valid'))
+})
+
+test('/redirect should not be server-rendered', async t => {
+ const html = await rp(url('/redirect'))
+ t.true(html.includes('
'))
+ t.true(html.includes('serverRendered:!1'))
+})
+
+test('/redirect -> check redirected source', async t => {
+ const window = await nuxt.renderAndGetWindow(url('/redirect'))
+ const html = window.document.body.innerHTML
+ t.true(html.includes('Index page '))
+})
+
+// Close server and ask nuxt to stop listening to file changes
+test.after('Closing server', t => {
+ server.close()
+})
diff --git a/test/basic.generate.test.js b/test/basic.generate.test.js
index cc0c160912..0e1020847e 100644
--- a/test/basic.generate.test.js
+++ b/test/basic.generate.test.js
@@ -1,9 +1,12 @@
import test from 'ava'
import { resolve } from 'path'
+import { existsSync } from 'fs'
import http from 'http'
import serveStatic from 'serve-static'
import finalhandler from 'finalhandler'
import rp from 'request-promise-native'
+import { Nuxt, Builder, Generator } from '../index.js'
+
const port = 4002
const url = (route) => 'http://localhost:' + port + route
@@ -12,15 +15,17 @@ let server = null
// Init nuxt.js and create server listening on localhost:4000
test.before('Init Nuxt.js', async t => {
- const Nuxt = require('../')
const rootDir = resolve(__dirname, 'fixtures/basic')
let config = require(resolve(rootDir, 'nuxt.config.js'))
config.rootDir = rootDir
config.dev = false
nuxt = new Nuxt(config)
+ const builder = new Builder(nuxt)
+ const generator = new Generator(nuxt, builder)
try {
- await nuxt.generate() // throw an error (of /validate route)
- } catch (err) {}
+ await generator.generate() // throw an error (of /validate route)
+ } catch (err) {
+ }
const serve = serveStatic(resolve(__dirname, 'fixtures/basic/dist'))
server = http.createServer((req, res) => {
serve(req, res, finalhandler(req, res))
@@ -28,6 +33,10 @@ test.before('Init Nuxt.js', async t => {
server.listen(port)
})
+test('Check ready hook called', async t => {
+ t.true(nuxt.__hook_called__)
+})
+
test('/stateless', async t => {
const window = await nuxt.renderAndGetWindow(url('/stateless'))
const html = window.document.body.innerHTML
@@ -67,6 +76,8 @@ test('/async-data', async t => {
test('/users/1', async t => {
const html = await rp(url('/users/1'))
t.true(html.includes('User: 1 '))
+ t.true(existsSync(resolve(__dirname, 'fixtures/basic/dist', 'users/1/index.html')))
+ t.false(existsSync(resolve(__dirname, 'fixtures/basic/dist', 'users/1.html')))
})
test('/users/2', async t => {
diff --git a/test/basic.test.js b/test/basic.ssr.test.js
similarity index 59%
rename from test/basic.test.js
rename to test/basic.ssr.test.js
index e064a5a4db..9b9a4d62a8 100755
--- a/test/basic.test.js
+++ b/test/basic.ssr.test.js
@@ -2,24 +2,28 @@ import test from 'ava'
import { resolve } from 'path'
import rp from 'request-promise-native'
import stdMocks from 'std-mocks'
+import { Nuxt, Builder } from '../index.js'
const port = 4003
const url = (route) => 'http://localhost:' + port + route
let nuxt = null
-let server = null
-// Init nuxt.js and create server listening on localhost:4000
+// Init nuxt.js and create server listening on localhost:4003
test.before('Init Nuxt.js', async t => {
- const Nuxt = require('../')
const options = {
rootDir: resolve(__dirname, 'fixtures/basic'),
- dev: false
+ dev: false,
+ head: {
+ titleTemplate(titleChunk) {
+ return titleChunk ? `${titleChunk} - Nuxt.js` : 'Nuxt.js'
+ }
+ }
}
nuxt = new Nuxt(options)
- await nuxt.build()
- server = new nuxt.Server(nuxt)
- server.listen(port, 'localhost')
+ await new Builder(nuxt).build()
+
+ await nuxt.listen(port, 'localhost')
})
test('/stateless', async t => {
@@ -51,12 +55,17 @@ test('/store', async t => {
})
test('/head', async t => {
+ stdMocks.use()
const window = await nuxt.renderAndGetWindow(url('/head'), { virtualConsole: false })
const html = window.document.body.innerHTML
const metas = window.document.getElementsByTagName('meta')
- t.is(window.document.title, 'My title')
+ stdMocks.restore()
+ const { stdout } = stdMocks.flush()
+ t.is(stdout[0], 'Body script!\n')
+ t.is(window.document.title, 'My title - Nuxt.js')
t.is(metas[0].getAttribute('content'), 'my meta')
t.true(html.includes('
I can haz meta tags '))
+ t.true(html.includes('
diff --git a/test/fixtures/basic/pages/callback-async-data.vue b/test/fixtures/basic/pages/callback-async-data.vue
index 7b0610e5ac..07481dd1fb 100644
--- a/test/fixtures/basic/pages/callback-async-data.vue
+++ b/test/fixtures/basic/pages/callback-async-data.vue
@@ -4,7 +4,7 @@
diff --git a/test/fixtures/basic/pages/error.vue b/test/fixtures/basic/pages/error.vue
index 19704e7ade..2b2308b386 100644
--- a/test/fixtures/basic/pages/error.vue
+++ b/test/fixtures/basic/pages/error.vue
@@ -4,11 +4,8 @@
diff --git a/test/fixtures/basic/pages/error2.vue b/test/fixtures/basic/pages/error2.vue
index 156a0e958c..252ad842a4 100644
--- a/test/fixtures/basic/pages/error2.vue
+++ b/test/fixtures/basic/pages/error2.vue
@@ -4,7 +4,7 @@
diff --git a/test/fixtures/basic/pages/head.vue b/test/fixtures/basic/pages/head.vue
index a4795f298b..1be13ec8e8 100755
--- a/test/fixtures/basic/pages/head.vue
+++ b/test/fixtures/basic/pages/head.vue
@@ -10,6 +10,9 @@ export default {
title: 'My title',
meta: [
{ content: 'my meta' }
+ ],
+ script: [
+ { src: '/body.js', body: true }
]
}
}
diff --git a/test/fixtures/basic/pages/meta.vue b/test/fixtures/basic/pages/meta.vue
new file mode 100644
index 0000000000..4649b02390
--- /dev/null
+++ b/test/fixtures/basic/pages/meta.vue
@@ -0,0 +1,12 @@
+
+ {{ $store.state.meta }}
+
+
+
diff --git a/test/fixtures/basic/pages/no-ssr.vue b/test/fixtures/basic/pages/no-ssr.vue
new file mode 100644
index 0000000000..67fa89a764
--- /dev/null
+++ b/test/fixtures/basic/pages/no-ssr.vue
@@ -0,0 +1,5 @@
+
+
+ Displayed only on client-side
+
+
diff --git a/test/fixtures/basic/pages/redirect.vue b/test/fixtures/basic/pages/redirect.vue
index dc5d2f0c85..111dc983fa 100644
--- a/test/fixtures/basic/pages/redirect.vue
+++ b/test/fixtures/basic/pages/redirect.vue
@@ -1,10 +1,10 @@
-
+ Redirecting...
diff --git a/test/fixtures/basic/pages/stateful.vue b/test/fixtures/basic/pages/stateful.vue
index 3931e79db1..27b5563ed9 100755
--- a/test/fixtures/basic/pages/stateful.vue
+++ b/test/fixtures/basic/pages/stateful.vue
@@ -6,10 +6,10 @@
diff --git a/test/fixtures/children/pages/parent/_id.vue b/test/fixtures/children/pages/parent/_id.vue
index 49c75c2544..8fbd0650a0 100644
--- a/test/fixtures/children/pages/parent/_id.vue
+++ b/test/fixtures/children/pages/parent/_id.vue
@@ -1,3 +1,20 @@
- Id={{ $route.params.id }}
+ Id={{ id }}
+
+
diff --git a/test/fixtures/children/pages/parent/validate-child.vue b/test/fixtures/children/pages/parent/validate-child.vue
index 4fd4d4bb2d..c832cd4be0 100644
--- a/test/fixtures/children/pages/parent/validate-child.vue
+++ b/test/fixtures/children/pages/parent/validate-child.vue
@@ -4,7 +4,7 @@
diff --git a/test/fixtures/children/pages/patch/_id.vue b/test/fixtures/children/pages/patch/_id.vue
new file mode 100644
index 0000000000..dc5940e40c
--- /dev/null
+++ b/test/fixtures/children/pages/patch/_id.vue
@@ -0,0 +1,16 @@
+
+
+
_id: {{ date }}
+
+
+
+
+
diff --git a/test/fixtures/children/pages/patch/_id/child.vue b/test/fixtures/children/pages/patch/_id/child.vue
new file mode 100644
index 0000000000..346f2123ab
--- /dev/null
+++ b/test/fixtures/children/pages/patch/_id/child.vue
@@ -0,0 +1,16 @@
+
+
+
child: {{ date }}
+
+
+
+
+
diff --git a/test/fixtures/children/pages/patch/_id/child/_slug.vue b/test/fixtures/children/pages/patch/_id/child/_slug.vue
new file mode 100644
index 0000000000..3ac44c444e
--- /dev/null
+++ b/test/fixtures/children/pages/patch/_id/child/_slug.vue
@@ -0,0 +1,54 @@
+
+
+
_slug: {{ date }}
+
+
+
+
+
+
diff --git a/test/fixtures/children/pages/patch/_id/index.vue b/test/fixtures/children/pages/patch/_id/index.vue
new file mode 100644
index 0000000000..d86b9896cf
--- /dev/null
+++ b/test/fixtures/children/pages/patch/_id/index.vue
@@ -0,0 +1,3 @@
+
+ Index
+
diff --git a/test/fixtures/children/pages/patch/index.vue b/test/fixtures/children/pages/patch/index.vue
new file mode 100644
index 0000000000..5d848671f3
--- /dev/null
+++ b/test/fixtures/children/pages/patch/index.vue
@@ -0,0 +1,3 @@
+
+ Index
+
diff --git a/test/fixtures/debug/nuxt.config.js b/test/fixtures/debug/nuxt.config.js
new file mode 100644
index 0000000000..2bb2464225
--- /dev/null
+++ b/test/fixtures/debug/nuxt.config.js
@@ -0,0 +1,13 @@
+module.exports = {
+ router: {
+ base: '/test/'
+ },
+ debug: true,
+ build: {
+ scopeHoisting: true
+ },
+ editor: {
+ cmd: 'echo',
+ pattern: ''
+ }
+}
diff --git a/test/fixtures/debug/pages/error.vue b/test/fixtures/debug/pages/error.vue
new file mode 100644
index 0000000000..9be9c6d927
--- /dev/null
+++ b/test/fixtures/debug/pages/error.vue
@@ -0,0 +1,12 @@
+
+ Error page
+
+
+
diff --git a/test/fixtures/dynamic-routes/pages/_.vue b/test/fixtures/debug/pages/index.vue
similarity index 100%
rename from test/fixtures/dynamic-routes/pages/_.vue
rename to test/fixtures/debug/pages/index.vue
diff --git a/test/fixtures/deprecate/modules/hooks/index.js b/test/fixtures/deprecate/modules/hooks/index.js
new file mode 100644
index 0000000000..883fe0e0ba
--- /dev/null
+++ b/test/fixtures/deprecate/modules/hooks/index.js
@@ -0,0 +1,6 @@
+module.exports = function () {
+ // Note: Plugin is deprecated. Please use new hooks system.
+ this.nuxt.plugin('built', (builder) => {
+ this.nuxt.__builder_plugin = true
+ })
+}
diff --git a/test/fixtures/deprecate/nuxt.config.js b/test/fixtures/deprecate/nuxt.config.js
new file mode 100755
index 0000000000..2fc7d1f9ba
--- /dev/null
+++ b/test/fixtures/deprecate/nuxt.config.js
@@ -0,0 +1,12 @@
+module.exports = {
+ modules: [
+ '~/modules/hooks'
+ ],
+ build: {
+ extend(config, options) {
+ if (options.dev) {
+ // Please use isDev instead of dev
+ }
+ }
+ }
+}
diff --git a/test/fixtures/deprecate/package.json b/test/fixtures/deprecate/package.json
new file mode 100755
index 0000000000..ccdd12ba4a
--- /dev/null
+++ b/test/fixtures/deprecate/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "deprecated-apis",
+ "version": "1.0.0",
+ "dependencies": {}
+}
diff --git a/test/fixtures/deprecate/pages/about.vue b/test/fixtures/deprecate/pages/about.vue
new file mode 100644
index 0000000000..93009e1b3e
--- /dev/null
+++ b/test/fixtures/deprecate/pages/about.vue
@@ -0,0 +1,6 @@
+
+
+
About page
+ Home page
+
+
diff --git a/test/fixtures/deprecate/pages/index.vue b/test/fixtures/deprecate/pages/index.vue
new file mode 100755
index 0000000000..600aa6eb97
--- /dev/null
+++ b/test/fixtures/deprecate/pages/index.vue
@@ -0,0 +1,13 @@
+
+
+
Home page
+ About page
+
+
+
+
diff --git a/test/fixtures/dll/nuxt.config.js b/test/fixtures/dll/nuxt.config.js
new file mode 100644
index 0000000000..d2f540265b
--- /dev/null
+++ b/test/fixtures/dll/nuxt.config.js
@@ -0,0 +1,12 @@
+module.exports = {
+ build: {
+ dll: true,
+ extend(config, options) {
+ if (options.isClient) {
+ const dlls = config.plugins.filter(plugin => plugin.constructor.name === 'DllReferencePlugin')
+ console.log('Using dll for ' + dlls.length + ' libs') // eslint-disable-line no-console
+ }
+ return config
+ }
+ }
+}
diff --git a/test/fixtures/dll/pages/index.vue b/test/fixtures/dll/pages/index.vue
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/dynamic-routes/pages/_/_.vue b/test/fixtures/dynamic-routes/pages/_/_.vue
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/dynamic-routes/pages/_/index.vue b/test/fixtures/dynamic-routes/pages/_/index.vue
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/dynamic-routes/pages/_/p/_.vue b/test/fixtures/dynamic-routes/pages/_/p/_.vue
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/dynamic-routes/pages/_key/_id.vue b/test/fixtures/dynamic-routes/pages/_key/_id.vue
index e69de29bb2..467caf2692 100644
--- a/test/fixtures/dynamic-routes/pages/_key/_id.vue
+++ b/test/fixtures/dynamic-routes/pages/_key/_id.vue
@@ -0,0 +1,3 @@
+
+ pages/_key/_id.vue
+
\ No newline at end of file
diff --git a/test/fixtures/dynamic-routes/pages/_slug.vue b/test/fixtures/dynamic-routes/pages/_slug.vue
index e69de29bb2..289c1300dc 100644
--- a/test/fixtures/dynamic-routes/pages/_slug.vue
+++ b/test/fixtures/dynamic-routes/pages/_slug.vue
@@ -0,0 +1,3 @@
+
+ pages/_slug.vue
+
\ No newline at end of file
diff --git a/test/fixtures/dynamic-routes/pages/test/_.vue b/test/fixtures/dynamic-routes/pages/test/_.vue
index e69de29bb2..41514424fa 100644
--- a/test/fixtures/dynamic-routes/pages/test/_.vue
+++ b/test/fixtures/dynamic-routes/pages/test/_.vue
@@ -0,0 +1,3 @@
+
+ pages/test/_.vue
+
\ No newline at end of file
diff --git a/test/fixtures/dynamic-routes/pages/test/index.vue b/test/fixtures/dynamic-routes/pages/test/index.vue
index e69de29bb2..87e2e6be6d 100644
--- a/test/fixtures/dynamic-routes/pages/test/index.vue
+++ b/test/fixtures/dynamic-routes/pages/test/index.vue
@@ -0,0 +1,3 @@
+
+ pages/test/index.vue
+
\ No newline at end of file
diff --git a/test/fixtures/module/modules/basic/index.js b/test/fixtures/module/modules/basic/index.js
index 864007865b..040ca4e5b8 100755
--- a/test/fixtures/module/modules/basic/index.js
+++ b/test/fixtures/module/modules/basic/index.js
@@ -1,6 +1,6 @@
const path = require('path')
-module.exports = function basicModule (options, resolve) {
+module.exports = function basicModule(options, resolve) {
// Add vendor
this.addVendor('lodash')
@@ -15,11 +15,13 @@ module.exports = function basicModule (options, resolve) {
// Extend build again
this.extendBuild((config, { isClient, isServer }) => {
// Do nothing!
+ return config
})
// Extend routes
this.extendRoutes((routes, resolve) => {
// Do nothing!
+ return routes
})
// Require same module twice
diff --git a/test/fixtures/module/modules/basic/reverse.js b/test/fixtures/module/modules/basic/reverse.js
index 39e01fba3b..6d4b9d356f 100755
--- a/test/fixtures/module/modules/basic/reverse.js
+++ b/test/fixtures/module/modules/basic/reverse.js
@@ -2,8 +2,10 @@
import Vue from 'vue'
-function $reverseStr (str) {
+function $reverseStr(str) {
return str.split('').reverse().join('')
}
Vue.prototype.$reverseStr = $reverseStr
+
+export default undefined
diff --git a/test/fixtures/module/modules/empty/index.js b/test/fixtures/module/modules/empty/index.js
index b994d6d28a..05c0b3f72e 100755
--- a/test/fixtures/module/modules/empty/index.js
+++ b/test/fixtures/module/modules/empty/index.js
@@ -1,4 +1,7 @@
-
-module.exports = function middlewareModule (options) {
+module.exports = function middlewareModule(options) {
// Empty module
}
+
+module.exports.meta = {
+ name: 'Empty Module!'
+}
diff --git a/test/fixtures/module/modules/hooks/index.js b/test/fixtures/module/modules/hooks/index.js
new file mode 100644
index 0000000000..5205db1861
--- /dev/null
+++ b/test/fixtures/module/modules/hooks/index.js
@@ -0,0 +1,27 @@
+module.exports = function () {
+ let ctr = 1
+
+ // Add hook for module
+ this.nuxt.hook('modules:done', (moduleContainer) => {
+ this.nuxt.__module_hook = moduleContainer && ctr++
+ })
+
+ // Add hook for renderer
+ this.nuxt.hook('render:done', (renderer) => {
+ this.nuxt.__renderer_hook = renderer && ctr++
+ })
+
+ // Add hook for build
+ this.nuxt.hook('build:done', (builder) => {
+ this.nuxt.__builder_hook = builder && ctr++
+ })
+
+ // Note: Plugin is deprecated. Please use new hooks system.
+ this.nuxt.plugin('built', (builder) => {
+ this.nuxt.__builder_plugin = builder && ctr++
+ })
+
+ this.nuxt.hook('build:extendRoutes', (builder) => {
+ throw new Error('hook error testing')
+ })
+}
diff --git a/test/fixtures/module/modules/middleware/index.js b/test/fixtures/module/modules/middleware/index.js
index d8e21640ab..33f1880806 100755
--- a/test/fixtures/module/modules/middleware/index.js
+++ b/test/fixtures/module/modules/middleware/index.js
@@ -1,10 +1,9 @@
-
-module.exports = function middlewareModule (options) {
+module.exports = function middlewareModule(options) {
return new Promise((resolve, reject) => {
// Add /api endpoint
this.addServerMiddleware({
path: '/api',
- handler (req, res, next) {
+ handler(req, res, next) {
res.end('It works!')
}
})
diff --git a/test/fixtures/module/modules/middleware/midd1.js b/test/fixtures/module/modules/middleware/midd1.js
index 4c109b5637..78ec0d0355 100644
--- a/test/fixtures/module/modules/middleware/midd1.js
+++ b/test/fixtures/module/modules/middleware/midd1.js
@@ -1,4 +1,4 @@
module.exports = function (req, res, next) {
- res.setHeader('x-midd-1', 'ok')
- next()
-}
\ No newline at end of file
+ res.setHeader('x-midd-1', 'ok')
+ next()
+}
diff --git a/test/fixtures/module/modules/middleware/midd2.js b/test/fixtures/module/modules/middleware/midd2.js
index 95ae3940dc..64babff282 100644
--- a/test/fixtures/module/modules/middleware/midd2.js
+++ b/test/fixtures/module/modules/middleware/midd2.js
@@ -1,4 +1,4 @@
module.exports = function (req, res, next) {
- res.setHeader('x-midd-2', 'ok')
- next()
-}
\ No newline at end of file
+ res.setHeader('x-midd-2', 'ok')
+ next()
+}
diff --git a/test/fixtures/module/modules/template/index.js b/test/fixtures/module/modules/template/index.js
index 7e85b80d56..6033364d55 100644
--- a/test/fixtures/module/modules/template/index.js
+++ b/test/fixtures/module/modules/template/index.js
@@ -1,11 +1,12 @@
const path = require('path')
module.exports = function () {
- // Disable parsing pages/
- this.nuxt.createRoutes = () => {}
- // Add /api endpoint
- this.addTemplate({
- fileName: 'router.js',
- src: path.resolve(this.nuxt.srcDir, 'router.js')
- })
+ // Disable parsing pages/
+ this.nuxt.options.build.createRoutes = () => {}
+
+ // Add /api endpoint
+ this.addTemplate({
+ fileName: 'router.js',
+ src: path.resolve(this.options.srcDir, 'router.js')
+ })
}
diff --git a/test/fixtures/module/nuxt.config.js b/test/fixtures/module/nuxt.config.js
index 19e591d11d..63b3a2f62a 100755
--- a/test/fixtures/module/nuxt.config.js
+++ b/test/fixtures/module/nuxt.config.js
@@ -1,7 +1,8 @@
module.exports = {
loading: true,
modules: [
- '~modules/basic',
+ '~~/modules/basic',
+ '~/modules/hooks',
{
src: '~/modules/middleware',
options: {
@@ -12,5 +13,13 @@ module.exports = {
],
serverMiddleware: [
'./modules/middleware/midd2'
- ]
+ ],
+ hooks(hook) {
+ hook('ready', nuxt => {
+ nuxt.__ready_called__ = true
+ })
+ hook('build:done', builder => {
+ builder.__build_done__ = true
+ })
+ }
}
diff --git a/test/fixtures/module/router.js b/test/fixtures/module/router.js
index 629c9d4ffc..3899de8cd8 100644
--- a/test/fixtures/module/router.js
+++ b/test/fixtures/module/router.js
@@ -3,20 +3,23 @@ import Router from 'vue-router'
Vue.use(Router)
-export function createRouter () {
+const indexPage = () => import('~/views/index.vue').then(m => m.default || m)
+const aboutPage = () => import('~/views/about.vue').then(m => m.default || m)
+
+export function createRouter() {
return new Router({
mode: 'history',
routes: [
- {
- path: "/",
- component: require('~/views/index.vue'),
- name: "index"
- },
- {
- path: "/about",
- component: require('~/views/about.vue'),
- name: "about"
- }
+ {
+ path: '/',
+ component: indexPage,
+ name: 'index'
+ },
+ {
+ path: '/about',
+ component: aboutPage,
+ name: 'about'
+ }
]
})
}
diff --git a/test/fixtures/spa/layouts/custom.vue b/test/fixtures/spa/layouts/custom.vue
new file mode 100644
index 0000000000..5a02389c29
--- /dev/null
+++ b/test/fixtures/spa/layouts/custom.vue
@@ -0,0 +1,7 @@
+
+
+ Custom layout
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/spa/layouts/default.vue b/test/fixtures/spa/layouts/default.vue
new file mode 100644
index 0000000000..fe69c5ced6
--- /dev/null
+++ b/test/fixtures/spa/layouts/default.vue
@@ -0,0 +1,7 @@
+
+
+ Default layout
+
+
+
+
diff --git a/test/fixtures/spa/nuxt.config.js b/test/fixtures/spa/nuxt.config.js
new file mode 100644
index 0000000000..bcbf687d27
--- /dev/null
+++ b/test/fixtures/spa/nuxt.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ rootDir: __dirname,
+ mode: 'spa',
+ dev: false,
+ transition: false
+}
diff --git a/test/fixtures/spa/pages/custom.vue b/test/fixtures/spa/pages/custom.vue
new file mode 100644
index 0000000000..ccd8675d81
--- /dev/null
+++ b/test/fixtures/spa/pages/custom.vue
@@ -0,0 +1,15 @@
+
+ Hello SPA!
+
+
+
diff --git a/test/fixtures/spa/pages/index.vue b/test/fixtures/spa/pages/index.vue
new file mode 100644
index 0000000000..c60926b6ac
--- /dev/null
+++ b/test/fixtures/spa/pages/index.vue
@@ -0,0 +1,12 @@
+
+ Hello SPA!
+
+
+
diff --git a/test/fixtures/ssr/components/test.vue b/test/fixtures/ssr/components/test.vue
new file mode 100644
index 0000000000..9a0b51634b
--- /dev/null
+++ b/test/fixtures/ssr/components/test.vue
@@ -0,0 +1,17 @@
+
+
+ {{id}}
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/ssr/lib/db.js b/test/fixtures/ssr/lib/db.js
new file mode 100644
index 0000000000..9c173807f6
--- /dev/null
+++ b/test/fixtures/ssr/lib/db.js
@@ -0,0 +1,4 @@
+
+let idCtr = 0
+
+export const nextId = () => ++idCtr
diff --git a/test/fixtures/ssr/pages/asyncComponent.vue b/test/fixtures/ssr/pages/asyncComponent.vue
new file mode 100644
index 0000000000..53b77b0d20
--- /dev/null
+++ b/test/fixtures/ssr/pages/asyncComponent.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/ssr/pages/asyncData.vue b/test/fixtures/ssr/pages/asyncData.vue
new file mode 100644
index 0000000000..f2883f6dc1
--- /dev/null
+++ b/test/fixtures/ssr/pages/asyncData.vue
@@ -0,0 +1,15 @@
+
+{{id}}
+
+
+
diff --git a/test/fixtures/ssr/pages/component.vue b/test/fixtures/ssr/pages/component.vue
new file mode 100644
index 0000000000..3ab75cac5b
--- /dev/null
+++ b/test/fixtures/ssr/pages/component.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/ssr/pages/data.vue b/test/fixtures/ssr/pages/data.vue
new file mode 100644
index 0000000000..fbf8c51259
--- /dev/null
+++ b/test/fixtures/ssr/pages/data.vue
@@ -0,0 +1,15 @@
+
+{{id}}
+
+
+
diff --git a/test/fixtures/ssr/pages/fetch.vue b/test/fixtures/ssr/pages/fetch.vue
new file mode 100644
index 0000000000..6accc75c5c
--- /dev/null
+++ b/test/fixtures/ssr/pages/fetch.vue
@@ -0,0 +1,14 @@
+
+{{$store.__id}}
+
+
+
diff --git a/test/fixtures/ssr/pages/store.vue b/test/fixtures/ssr/pages/store.vue
new file mode 100644
index 0000000000..767e0f3b23
--- /dev/null
+++ b/test/fixtures/ssr/pages/store.vue
@@ -0,0 +1,5 @@
+
+
+ {{$store.state[$route.query.onServerInit === '1' ? 'id2': 'id']}}
+
+
\ No newline at end of file
diff --git a/test/fixtures/ssr/store/index.js b/test/fixtures/ssr/store/index.js
new file mode 100644
index 0000000000..4e5552d398
--- /dev/null
+++ b/test/fixtures/ssr/store/index.js
@@ -0,0 +1,22 @@
+import { nextId } from '@/lib/db'
+
+export const state = () => {
+ return {
+ id: nextId(),
+ id2: 0
+ }
+}
+
+export const mutations = {
+ setId2(state, id) {
+ state.id2 = id
+ }
+}
+
+export const actions = {
+ nuxtServerInit({ commit, state }, { route }) {
+ if (route.query.onServerInit === '1') {
+ commit('setId2', nextId())
+ }
+ }
+}
diff --git a/test/fixtures/with-config/assets/app.css b/test/fixtures/with-config/assets/app.css
index f69fcd8bbc..02152c1c08 100755
--- a/test/fixtures/with-config/assets/app.css
+++ b/test/fixtures/with-config/assets/app.css
@@ -1,3 +1,10 @@
.global-css-selector {
color: red;
}
+
+.test-enter-active, .test-leave-active {
+ transition: opacity .5s
+}
+.test-enter, .test-leave-active {
+ opacity: 0
+}
diff --git a/test/fixtures/with-config/assets/roboto.woff2 b/test/fixtures/with-config/assets/roboto.woff2
new file mode 100644
index 0000000000..555f98b120
Binary files /dev/null and b/test/fixtures/with-config/assets/roboto.woff2 differ
diff --git a/test/fixtures/with-config/components/loading.vue b/test/fixtures/with-config/components/loading.vue
index 9c28022726..4cec640bfc 100644
--- a/test/fixtures/with-config/components/loading.vue
+++ b/test/fixtures/with-config/components/loading.vue
@@ -10,10 +10,10 @@ export default {
loading: false
}),
methods: {
- start () {
+ start() {
this.loading = true
},
- finish () {
+ finish() {
this.loading = false
}
}
diff --git a/test/fixtures/with-config/layouts/default.vue b/test/fixtures/with-config/layouts/default.vue
index 4555ce65c4..4ab9402ac2 100644
--- a/test/fixtures/with-config/layouts/default.vue
+++ b/test/fixtures/with-config/layouts/default.vue
@@ -4,3 +4,17 @@
+
+
\ No newline at end of file
diff --git a/test/fixtures/with-config/layouts/desktop/default.vue b/test/fixtures/with-config/layouts/desktop/default.vue
new file mode 100644
index 0000000000..329274e836
--- /dev/null
+++ b/test/fixtures/with-config/layouts/desktop/default.vue
@@ -0,0 +1,20 @@
+
+
+
Default desktop layout
+
+
+
+
+
diff --git a/test/fixtures/with-config/layouts/mobile/default.vue b/test/fixtures/with-config/layouts/mobile/default.vue
new file mode 100644
index 0000000000..548fe9b27b
--- /dev/null
+++ b/test/fixtures/with-config/layouts/mobile/default.vue
@@ -0,0 +1,21 @@
+
+
+
Default mobile layout
+
+
+
+
+
+
diff --git a/test/fixtures/with-config/middleware/noop.js b/test/fixtures/with-config/middleware/noop.js
index 26f7210f3d..335d554e10 100644
--- a/test/fixtures/with-config/middleware/noop.js
+++ b/test/fixtures/with-config/middleware/noop.js
@@ -1,3 +1,3 @@
export default function () {
- // NOOP!
-}
\ No newline at end of file
+ // NOOP!
+}
diff --git a/test/fixtures/with-config/middleware/user-agent.js b/test/fixtures/with-config/middleware/user-agent.js
index 097436a73d..662a76ef02 100644
--- a/test/fixtures/with-config/middleware/user-agent.js
+++ b/test/fixtures/with-config/middleware/user-agent.js
@@ -1,3 +1,3 @@
export default function (context) {
- context.userAgent = context.isServer ? context.req.headers['user-agent'] : navigator.userAgent
+ context.userAgent = process.server ? context.req.headers['user-agent'] : navigator.userAgent
}
diff --git a/test/fixtures/with-config/nuxt.config.js b/test/fixtures/with-config/nuxt.config.js
index f9e56aa032..34012a7fc1 100644
--- a/test/fixtures/with-config/nuxt.config.js
+++ b/test/fixtures/with-config/nuxt.config.js
@@ -1,44 +1,68 @@
+const path = require('path')
+
module.exports = {
srcDir: __dirname,
router: {
base: '/test/',
middleware: 'noop',
- extendRoutes (routes) {
- routes.push({
- name: 'about-bis',
- path: '/about-bis',
- component: '~pages/about.vue'
- })
+ extendRoutes(routes) {
+ return [
+ ...routes,
+ {
+ name: 'about-bis',
+ path: '/about-bis',
+ component: '~/pages/about.vue'
+ }
+ ]
}
},
+ modulesDir: path.join(__dirname, '..', '..', '..', 'node_modules'),
transition: 'test',
+ layoutTransition: 'test',
offline: true,
plugins: [
- '~plugins/test.js',
- { src: '~plugins/offline.js', ssr: false },
- { src: '~plugins/only-client.js', ssr: false }
+ '~/plugins/test.js',
+ { src: '~/plugins/only-client.js', ssr: false }
],
- loading: '~components/loading',
+ loading: '~/components/loading',
env: {
bool: true,
num: 23,
- string: 'Nuxt.js'
+ string: 'Nuxt.js',
+ object: {
+ bool: false,
+ string: 'ok',
+ num2: 8.23,
+ obj: {
+ again: true
+ }
+ }
},
build: {
- extractCSS: true,
+ // extractCSS: true,
publicPath: '/orion/',
analyze: {
analyzerMode: 'disabled',
generateStatsFile: true
},
- extend (config, options) {
- config.devtool = 'nosources-source-map'
+ extend(config, options) {
+ return Object.assign({}, config, {
+ devtool: 'nosources-source-map'
+ })
}
},
css: [
{ src: '~/assets/app.css' }
],
render: {
+ http2: {
+ push: true
+ },
+ bundleRenderer: {
+ shouldPreload: (file, type) => {
+ return ['script', 'style', 'font'].includes(type)
+ }
+ },
static: {
maxAge: '1y'
}
diff --git a/test/fixtures/with-config/pages/desktop.vue b/test/fixtures/with-config/pages/desktop.vue
new file mode 100644
index 0000000000..4c0951062e
--- /dev/null
+++ b/test/fixtures/with-config/pages/desktop.vue
@@ -0,0 +1,12 @@
+
+
+
Desktop page
+
+
+
+
+
diff --git a/test/fixtures/with-config/pages/env.vue b/test/fixtures/with-config/pages/env.vue
index df9f95d882..1c2dd3419e 100644
--- a/test/fixtures/with-config/pages/env.vue
+++ b/test/fixtures/with-config/pages/env.vue
@@ -1,11 +1,20 @@
- {{ env }}
+
+
{{ env }}
+
object:
+
{{ processEnv }}
+
Home
+
+
diff --git a/test/fixtures/with-config/pages/user-agent.vue b/test/fixtures/with-config/pages/user-agent.vue
index dff65e97d8..904156ace0 100644
--- a/test/fixtures/with-config/pages/user-agent.vue
+++ b/test/fixtures/with-config/pages/user-agent.vue
@@ -5,7 +5,7 @@