Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: .nuxtignore #4647

Merged
merged 35 commits into from Jan 29, 2019
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6c7b938
feat: .nuxtignore in pages and layouts
clarkdo Dec 28, 2018
04a1c96
refactor: make code clear
clarkdo Dec 28, 2018
77b00bc
fix: test
clarkdo Dec 28, 2018
5660631
fix(test): move test into correct suite
clarkdo Dec 28, 2018
ced2929
refactor: extract resolveFiles
clarkdo Dec 28, 2018
c5eb385
feat: ignore stores
clarkdo Dec 31, 2018
bf6c63c
Merge remote-tracking branch 'origin/dev' into feat/nuxtignore
clarkdo Dec 31, 2018
f97b388
fix: lint
clarkdo Dec 31, 2018
a52d4cc
feat: ignore middleware
clarkdo Dec 31, 2018
08cb300
Merge branch 'dev' into feat/nuxtignore
clarkdo Dec 31, 2018
20ba116
Merge branch 'dev' into feat/nuxtignore
clarkdo Jan 2, 2019
fcf9258
feat: watch ignore file
clarkdo Dec 31, 2018
bd82c20
refactor: move ignore.js to utils
clarkdo Jan 2, 2019
f6dc3d0
Merge branch 'dev' into feat/nuxtignore
pi0 Jan 6, 2019
fbe7081
refactor: simplify forEach
clarkdo Jan 7, 2019
a16dd6e
refactor: move ignore back to builder
clarkdo Jan 7, 2019
b03e3a5
refactor: better name for storeModules
clarkdo Jan 7, 2019
445b49c
fix: syntax error
clarkdo Jan 7, 2019
24b0c2a
Merge branch 'dev' into feat/nuxtignore
clarkdo Jan 7, 2019
33fd252
refactor: use array.map in resolveRelative
clarkdo Jan 7, 2019
2e68f4a
Merge remote-tracking branch 'origin/dev' into feat/nuxtignore
clarkdo Jan 7, 2019
bd96c01
Merge branch 'dev' of github.com:nuxt/nuxt.js into feat/nuxtignore
clarkdo Jan 7, 2019
7e52f3b
Merge remote-tracking branch 'upstream/dev' into feat/nuxtignore
clarkdo Jan 14, 2019
831482f
fix: missing configLayouts
clarkdo Jan 14, 2019
f932271
Merge branch 'dev' into feat/nuxtignore
manniL Jan 15, 2019
fba7692
Merge branch 'dev' of github.com:nuxt/nuxt.js into feat/nuxtignore
clarkdo Jan 23, 2019
487b248
feat: support ignore store
clarkdo Jan 23, 2019
6df17d9
test: ignore test
clarkdo Jan 23, 2019
12a67ac
plugins is unnecessary to support ingore
clarkdo Jan 23, 2019
25d5ab6
test: ignore layout
clarkdo Jan 23, 2019
00b7eca
fix: wrong dir in resolving layouts
clarkdo Jan 23, 2019
249daaf
Merge branch 'dev' into feat/nuxtignore
clarkdo Jan 23, 2019
31f81f0
Merge branch 'dev' into feat/nuxtignore
clarkdo Jan 23, 2019
fde1008
Merge branch 'dev' into feat/nuxtignore
clarkdo Jan 28, 2019
3f2ab41
Merge branch 'dev' into feat/nuxtignore
clarkdo Jan 29, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/builder/package.json
Expand Up @@ -16,6 +16,7 @@
"fs-extra": "^7.0.1",
"glob": "^7.1.3",
"hash-sum": "^1.0.2",
"ignore": "^5.0.4",
clarkdo marked this conversation as resolved.
Show resolved Hide resolved
"lodash": "^4.17.11",
"pify": "^4.0.1",
"semver": "^5.6.0",
Expand Down
56 changes: 42 additions & 14 deletions packages/builder/src/builder.js
Expand Up @@ -31,6 +31,7 @@ import {
isIndexFileAndFolder
} from '@nuxt/utils'

import Ignore from './ignore'
import BuildContext from './context'

const glob = pify(Glob)
Expand Down Expand Up @@ -86,6 +87,9 @@ export default class Builder {
// }

this.bundleBuilder = this.getBundleBuilder(bundleBuilder)
this.ignore = new Ignore({
rootDir: this.options.srcDir
})
}

getBundleBuilder(BundleBuilder) {
Expand Down Expand Up @@ -132,6 +136,18 @@ export default class Builder {
)
}

async resolveFiles(dir, cwd = this.options.srcDir) {
return this.ignore.filter(await glob(`${dir}/**/*.{${this.supportedExtensions.join(',')}}`, {
cwd,
ignore: this.options.ignore
}))
}

async resolveRelative(dir) {
const dirPrefix = new RegExp(`^${dir}/`)
return (await this.resolveFiles(dir)).map(file => ({ src: file.replace(dirPrefix, '') }))
}

resolvePlugins() {
// Check plugins exist then set alias to their real path
return Promise.all(this.plugins.map(async (p) => {
Expand Down Expand Up @@ -318,14 +334,12 @@ export default class Builder {
router: this.options.router,
env: this.options.env,
head: this.options.head,
middleware: fsExtra.existsSync(path.join(this.options.srcDir, this.options.dir.middleware)),
store: this.options.store,
globalName: this.options.globalName,
globals: this.globals,
css: this.options.css,
plugins: this.plugins,
appPath: './App.js',
ignorePrefix: this.options.ignorePrefix,
pi0 marked this conversation as resolved.
Show resolved Hide resolved
layouts: Object.assign({}, this.options.layouts),
loading:
typeof this.options.loading === 'string'
Expand All @@ -344,10 +358,7 @@ export default class Builder {
// -- Layouts --
if (fsExtra.existsSync(path.resolve(this.options.srcDir, this.options.dir.layouts))) {
const configLayouts = this.options.layouts
const layoutsFiles = await glob(`${this.options.dir.layouts}/**/*.{${this.supportedExtensions.join(',')}}`, {
cwd: this.options.srcDir,
ignore: this.options.ignore
})
const layoutsFiles = await this.resolveFiles(this.options.dir.layouts)
layoutsFiles.forEach((file) => {
const name = file
.replace(new RegExp(`^${this.options.dir.layouts}/`), '')
Expand Down Expand Up @@ -392,16 +403,14 @@ export default class Builder {
} else if (this._nuxtPages) {
// Use nuxt.js createRoutes bases on pages/
const files = {}
;(await glob(`${this.options.dir.pages}/**/*.{${this.supportedExtensions.join(',')}}`, {
cwd: this.options.srcDir,
ignore: this.options.ignore
})).forEach((f) => {
const key = f.replace(new RegExp(`\\.(${this.supportedExtensions.join('|')})$`), '')
const ext = new RegExp(`\\.(${this.supportedExtensions.join('|')})$`)
pi0 marked this conversation as resolved.
Show resolved Hide resolved
for (const page of await this.resolveFiles(this.options.dir.pages)) {
const key = page.replace(ext, '')
// .vue file takes precedence over other extensions
if (/\.vue$/.test(f) || !files[key]) {
files[key] = f.replace(/(['"])/g, '\\$1')
if (/\.vue$/.test(page) || !files[key]) {
manniL marked this conversation as resolved.
Show resolved Hide resolved
files[key] = page.replace(/(['"])/g, '\\$1')
}
})
}
templateVars.router.routes = createRoutes(
Object.values(files),
this.options.srcDir,
Expand Down Expand Up @@ -438,9 +447,24 @@ export default class Builder {
// -- Store --
// Add store if needed
if (this.options.store) {
templateVars.storeModules = (await this.resolveRelative(this.options.dir.store))
.sort(({ src: p1 }, { src: p2 }) => {
// modules are sorted from low to high priority (for overwriting properties)
let res = p1.split('/').length - p2.split('/').length
if (res === 0 && p1.includes('/index.')) {
res = -1
} else if (res === 0 && p2.includes('/index.')) {
res = 1
}
return res
})

templatesFiles.push('store.js')
}

// -- Middleware --
templateVars.middleware = await this.resolveRelative(this.options.dir.middleware)

// Resolve template files
const customTemplateFiles = this.options.build.templates.map(
t => t.dst || path.basename(t.src || t)
Expand Down Expand Up @@ -676,6 +700,10 @@ export default class Builder {
...this.options.watch
].map(this.nuxt.resolver.resolveAlias)

if (this.ignore.ignoreFile) {
nuxtRestartWatch.push(this.ignore.ignoreFile)
}

this.watchers.restart = chokidar
.watch(nuxtRestartWatch, this.options.watchers.chokidar)
.on('all', (event, _path) => {
Expand Down
51 changes: 51 additions & 0 deletions packages/builder/src/ignore.js
@@ -0,0 +1,51 @@
import path from 'path'
import fs from 'fs-extra'
import ignore from 'ignore'

export default class Ignore {
constructor(options) {
this.rootDir = options.rootDir
this.addIgnoresRules()
}

static get IGNORE_FILENAME() {
return '.nuxtignore'
}

findIgnoreFile() {
if (!this.ignoreFile) {
const ignoreFile = path.resolve(this.rootDir, Ignore.IGNORE_FILENAME)
if (fs.existsSync(ignoreFile) && fs.statSync(ignoreFile).isFile()) {
this.ignoreFile = ignoreFile
this.ignore = ignore()
}
}
return this.ignoreFile
}

readIgnoreFile() {
if (this.findIgnoreFile()) {
return fs.readFileSync(this.ignoreFile, 'utf8')
}
}

addIgnoresRules() {
const content = this.readIgnoreFile()
if (content) {
this.ignore.add(content)
}
}

filter(paths) {
if (this.ignore) {
return this.ignore.filter([].concat(paths || []))
}
return paths
}

reload() {
delete this.ignore
delete this.ignoreFile
this.addIgnoresRules()
}
}
22 changes: 6 additions & 16 deletions packages/vue-app/template/middleware.js
@@ -1,18 +1,8 @@
<% if (middleware) { %>
const files = require.context('@/<%= dir.middleware %>', false, /^\.\/(?!<%= ignorePrefix %>)[^.]+\.(<%= extensions %>)$/)
const filenames = files.keys()

function getModule(filename) {
const file = files(filename)
return file.default || file
}
const middleware = {}

// Generate the middleware
for (const filename of filenames) {
const name = filename.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '')
middleware[name] = getModule(filename)
}

<% middleware.forEach(m => {
const name = m.src.replace(new RegExp(`\\.(${extensions})$`), '')
%>
middleware['<%= name %>'] = require('@/<%= dir.middleware %>/<%= m.src %>');
middleware['<%= name %>'] = middleware['<%= name %>'].default || middleware['<%= name %>']
<% }) %>
export default middleware
<% } else { %>export default {}<% } %>
135 changes: 65 additions & 70 deletions packages/vue-app/template/store.js
Expand Up @@ -6,29 +6,12 @@ Vue.use(Vuex)
const log = console // on server-side, consola will catch all console.log
const VUEX_PROPERTIES = ['state', 'getters', 'actions', 'mutations']
let store = {}
let fileResolver

void (function updateModules() {
fileResolver = require.context('@/<%= dir.store %>', true, /^\.\/(?!<%= ignorePrefix %>)[^.]+\.(<%= extensions %>)$/)

// Paths are sorted from low to high priority (for overwriting properties)
const paths = fileResolver.keys().sort((p1, p2) => {
let res = p1.split('/').length - p2.split('/').length

if (res === 0 && p1.includes('/index.')) {
res = -1
} else if (res === 0 && p2.includes('/index.')) {
res = 1
}
return res
})

// Check if {dir.store}/index.js exists
const indexPath = paths.find(path => path.includes('./index.'))

if (indexPath) {
store = requireModule(indexPath, { isRoot: true })
}
<% storeModules.some(s => {
if(s.src.indexOf('index.') === 0) { %>
store = normalizeRoot(require('@/<%= dir.store %>/<%= s.src %>'), '<%= dir.store %>/<%= s.src %>')
<% return true }}) %>

// If store is an exported method = classic mode (deprecated)
if (typeof store === 'function') {
Expand All @@ -38,47 +21,17 @@ void (function updateModules() {
// Enforce store modules
store.modules = store.modules || {}

for (const path of paths) {
// Remove store path + extension (./foo/index.js -> foo/index)
const namespace = path.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '')

// Ignore indexFile, handled before
if (namespace === 'index') {
continue
}

const namespaces = namespace.split('/')
let moduleName = namespaces[namespaces.length - 1]
const moduleData = requireModule(path, { isState: moduleName === 'state' })

// If path is a known Vuex property
if (VUEX_PROPERTIES.includes(moduleName)) {
const property = moduleName
const storeModule = getStoreModule(store, namespaces, { isProperty: true })

// Replace state since it's a function
mergeProperty(storeModule, moduleData, property)
continue
}

// If file is foo/index.js, it should be saved as foo
const isIndexModule = (moduleName === 'index')
if (isIndexModule) {
namespaces.pop()
moduleName = namespaces[namespaces.length - 1]
}

const storeModule = getStoreModule(store, namespaces)

for (const property of VUEX_PROPERTIES) {
mergeProperty(storeModule, moduleData[property], property)
}
}
<% storeModules.forEach(s => {
if(s.src.indexOf('index.') !== 0) { %>
resolveStoreModules(require('@/<%= dir.store %>/<%= s.src %>'), '<%= s.src %>')<% }}) %>

// If the environment supports hot reloading...
<% if (isDev) { %>
if (process.client && module.hot) {
// Whenever any Vuex module is updated...
module.hot.accept(fileResolver.id, () => {
module.hot.accept([<% storeModules.forEach(s => { %>
'@/<%= dir.store %>/<%= s.src %>',<% }) %>
], () => {
// Update `root.modules` with the latest definitions.
updateModules()
// Trigger a hot update in the store.
Expand All @@ -94,26 +47,68 @@ export const createStore = store instanceof Function ? store : () => {
}, store))
}

// Dynamically require module
function requireModule(path, { isRoot = false, isState = false } = {}) {
const file = fileResolver(path)
let moduleData = file.default || file
function resolveStoreModules(moduleData, filename) {
moduleData = moduleData.default || moduleData
// Remove store src + extension (./foo/index.js -> foo/index)
const namespace = filename.replace(/\.(<%= extensions %>)$/, '')
const namespaces = namespace.split('/')
let moduleName = namespaces[namespaces.length - 1]
const filePath = `<%= dir.store %>/${filename}`

moduleData = moduleName === 'state'
? normalizeState(moduleData, filePath)
: normalizeModule(moduleData, filePath)

// If src is a known Vuex property
if (VUEX_PROPERTIES.includes(moduleName)) {
const property = moduleName
const storeModule = getStoreModule(store, namespaces, { isProperty: true })

// Replace state since it's a function
mergeProperty(storeModule, moduleData, property)
return
}

if (isState && typeof moduleData !== 'function') {
log.warn(`${path} should export a method that returns an object`)
const state = Object.assign({}, moduleData)
return () => state
// If file is foo/index.js, it should be saved as foo
const isIndexModule = (moduleName === 'index')
if (isIndexModule) {
namespaces.pop()
moduleName = namespaces[namespaces.length - 1]
}
if (isRoot && moduleData.commit) {
throw new Error('[nuxt] <%= dir.store %>/' + path.replace('./', '') + ' should export a method that returns a Vuex instance.')

const storeModule = getStoreModule(store, namespaces)

for (const property of VUEX_PROPERTIES) {
mergeProperty(storeModule, moduleData[property], property)
}
}

function normalizeRoot(moduleData, filePath) {
moduleData = moduleData.default || moduleData

if (moduleData.commit) {
throw new Error(`[nuxt] ${filePath} should export a method that returns a Vuex instance.`)
}

if (isRoot && typeof moduleData !== 'function') {
if (typeof moduleData !== 'function') {
// Avoid TypeError: setting a property that has only a getter when overwriting top level keys
moduleData = Object.assign({}, moduleData)
}
return normalizeModule(moduleData, filePath)
}

function normalizeState(moduleData, filePath) {
if (typeof moduleData !== 'function') {
log.warn(`${filePath} should export a method that returns an object`)
const state = Object.assign({}, moduleData)
return () => state
}
return normalizeModule(moduleData, filePath)
}

function normalizeModule(moduleData, filePath) {
if (moduleData.state && typeof moduleData.state !== 'function') {
log.warn(`'state' should be a method that returns an object in ${path}`)
log.warn(`'state' should be a method that returns an object in ${filePath}`)
const state = Object.assign({}, moduleData.state)
// Avoid TypeError: setting a property that has only a getter when overwriting top level keys
moduleData = Object.assign({}, moduleData, { state: () => state })
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/with-config/.nuxtignore
@@ -0,0 +1,4 @@
layouts/test-ignore.vue
pages/test-ignore.vue
store/test-ignore.js
middleware/test-ignore.js
1 change: 1 addition & 0 deletions test/fixtures/with-config/layouts/test-ignore.vue
@@ -0,0 +1 @@
throw new Error('This file should be ignored!!')
1 change: 1 addition & 0 deletions test/fixtures/with-config/middleware/test-ignore.js
@@ -0,0 +1 @@
throw new Error('This file should be ignored!!')
Empty file.
1 change: 1 addition & 0 deletions test/fixtures/with-config/store/test-ignore.js
@@ -0,0 +1 @@
throw new Error('This file should be ignored!!')