mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
feat: .nuxtignore (#4647)
This commit is contained in:
parent
a303ea89b3
commit
59be77a2f3
@ -16,6 +16,7 @@
|
||||
"fs-extra": "^7.0.1",
|
||||
"glob": "^7.1.3",
|
||||
"hash-sum": "^1.0.2",
|
||||
"ignore": "^5.0.4",
|
||||
"lodash": "^4.17.11",
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0",
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
isIndexFileAndFolder
|
||||
} from '@nuxt/utils'
|
||||
|
||||
import Ignore from './ignore'
|
||||
import BuildContext from './context'
|
||||
|
||||
const glob = pify(Glob)
|
||||
@ -86,6 +87,9 @@ export default class Builder {
|
||||
// }
|
||||
|
||||
this.bundleBuilder = this.getBundleBuilder(bundleBuilder)
|
||||
this.ignore = new Ignore({
|
||||
rootDir: this.options.srcDir
|
||||
})
|
||||
}
|
||||
|
||||
getBundleBuilder(BundleBuilder) {
|
||||
@ -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) => {
|
||||
@ -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,
|
||||
layouts: Object.assign({}, this.options.layouts),
|
||||
loading:
|
||||
typeof this.options.loading === 'string'
|
||||
@ -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}/`), '')
|
||||
@ -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('|')})$`)
|
||||
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]) {
|
||||
files[key] = page.replace(/(['"])/g, '\\$1')
|
||||
}
|
||||
})
|
||||
}
|
||||
templateVars.router.routes = createRoutes(
|
||||
Object.values(files),
|
||||
this.options.srcDir,
|
||||
@ -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)
|
||||
@ -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) => {
|
||||
|
51
packages/builder/src/ignore.js
Normal file
51
packages/builder/src/ignore.js
Normal file
@ -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()
|
||||
}
|
||||
}
|
@ -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 {}<% } %>
|
||||
|
@ -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') {
|
||||
@ -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 %>)$/, '')
|
||||
<% storeModules.forEach(s => {
|
||||
if(s.src.indexOf('index.') !== 0) { %>
|
||||
resolveStoreModules(require('@/<%= dir.store %>/<%= s.src %>'), '<%= s.src %>')<% }}) %>
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
@ -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}`
|
||||
|
||||
if (isState && typeof moduleData !== 'function') {
|
||||
log.warn(`${path} should export a method that returns an object`)
|
||||
const state = Object.assign({}, moduleData)
|
||||
return () => state
|
||||
}
|
||||
if (isRoot && moduleData.commit) {
|
||||
throw new Error('[nuxt] <%= dir.store %>/' + path.replace('./', '') + ' should export a method that returns a Vuex instance.')
|
||||
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 (isRoot && typeof moduleData !== 'function') {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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 (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 })
|
||||
|
4
test/fixtures/with-config/.nuxtignore
vendored
Normal file
4
test/fixtures/with-config/.nuxtignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
layouts/test-ignore.vue
|
||||
pages/test-ignore.vue
|
||||
store/test-ignore.js
|
||||
middleware/test-ignore.js
|
1
test/fixtures/with-config/layouts/test-ignore.vue
vendored
Normal file
1
test/fixtures/with-config/layouts/test-ignore.vue
vendored
Normal file
@ -0,0 +1 @@
|
||||
throw new Error('This file should be ignored!!')
|
1
test/fixtures/with-config/middleware/test-ignore.js
vendored
Normal file
1
test/fixtures/with-config/middleware/test-ignore.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
throw new Error('This file should be ignored!!')
|
0
test/fixtures/with-config/pages/test-ignore.vue
vendored
Normal file
0
test/fixtures/with-config/pages/test-ignore.vue
vendored
Normal file
1
test/fixtures/with-config/store/test-ignore.js
vendored
Normal file
1
test/fixtures/with-config/store/test-ignore.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
throw new Error('This file should be ignored!!')
|
@ -180,6 +180,11 @@ describe('with-config', () => {
|
||||
.rejects.toMatchObject({ statusCode: 404 })
|
||||
})
|
||||
|
||||
test('should ignore files in .nuxtignore', async () => {
|
||||
await expect(rp(url('/test-ignore')))
|
||||
.rejects.toMatchObject({ statusCode: 404 })
|
||||
})
|
||||
|
||||
test('renderAndGetWindow options', async () => {
|
||||
const fakeErrorLog = jest.fn()
|
||||
const mockOptions = {
|
||||
|
@ -5478,7 +5478,7 @@ ignore@^4.0.6:
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
||||
|
||||
ignore@^5.0.2:
|
||||
ignore@^5.0.2, ignore@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.0.4.tgz#33168af4a21e99b00c5d41cbadb6a6cb49903a45"
|
||||
integrity sha512-WLsTMEhsQuXpCiG173+f3aymI43SXa+fB1rSfbzyP4GkPP+ZFVuO0/3sFUGNBtifisPeDcl/uD/Y2NxZ7xFq4g==
|
||||
|
Loading…
Reference in New Issue
Block a user