mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Add .jsx extension support (#3376)
* Add .jsx extension * examples: add create-next-app (#3377) * examples: add create-next-app * fix with-typescript readme * Upgrading with-flow example to the latest flow-bin ver. 0.59.0 (#3337) For upgrading I used flow-upgrade module by https://yarnpkg.com/en/package/flow-upgrade * doc'd fs-routing option & added note on `passHref` (#3384) 2 changes: `passHref` - just added a cautionary note on the importance of `passHref`. We had a few days of no-href links on our site b/c we used a custom component instead of a raw `<a>` tag, and Google bot wasn't crawling our links (confirmed in Google cache). Hurt our SEO a bit, so I thought it was worth noting. `useFileSystemPublicRoutes` - this is mentioned in https://github.com/zeit/next.js/pull/914 , but it doesn't appear any doc was actually added. We use `next-routes`, and we were serving all the files in `/pages/` in addition to their route patterns (ie duplicate content), which can be a pain w/ SEO and duplicate content. * fix typo in readme.md (#3385) * Upgrade styled-jsx to v2.2.1 (#3358) * Pulled encoding to top of head (#3214) * Remove next.d.ts to use @types/next (#3297) * Add with-mobx-state-tree example (#3179) * Adapt with-mobx example for with-mobx-state-tree * Remove unnecessary lastUpdate parameter to show off snapshot * update readme * make other.js more closely mimic index.js * Upgrade styled-jsx to v2.2.1 Includes some bug fixes. * Fix linting * Make sure import that doesn’t end in .jsx works * Move tests * Show error when .js and .jsx both exist * Remove .jsx when importing from ‘path.jsx’ * Fixes * Get .jsx resolver back * Revert "Get .jsx resolver back" This reverts commit 6f76712caa400e6f41a6a32ff80189a95b194cce. * Revert "Revert "Get .jsx resolver back"" This reverts commit 69e592e86e53f28d0e1f78009196b76f2f831866. * Add remove .jsx to preset * Remove jsx resolver * Revert "Remove jsx resolver" This reverts commit 5e3ef1aca134de47657d91485809cd801e13329f. * Revert "Revert "Remove jsx resolver"" This reverts commit 8248e5066cff1c7e33dac2e5a88ffe6856e3fc4e. * Revert "Revert "Revert "Remove jsx resolver""" This reverts commit 2a6d418a227ea4e59874b0374628ef497e527c52. * Make 1 component not use .jsx
This commit is contained in:
parent
4be6a521ed
commit
e2bcb039cf
15
server/build/babel/plugins/remove-dotjsx-from-import.js
Normal file
15
server/build/babel/plugins/remove-dotjsx-from-import.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
// This plugins removes the `.jsx` extension from import statements. Because we transpile .jsx files to .js in .next
|
||||
// E.g. `import Hello from '../components/hello.jsx'` will become `import Hello from '../components/hello'`
|
||||
module.exports = function ({types}) {
|
||||
return {
|
||||
name: 'remove-dotjsx-from-import',
|
||||
visitor: {
|
||||
ImportDeclaration (path) {
|
||||
const value = path.node.source.value
|
||||
if (value.slice(-4) === '.jsx') {
|
||||
path.node.source = types.stringLiteral(value.slice(0, -4))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ module.exports = (context, opts = {}) => ({
|
|||
require.resolve('babel-preset-react')
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('./plugins/remove-dotjsx-from-import'),
|
||||
require.resolve('babel-plugin-react-require'),
|
||||
require.resolve('./plugins/handle-import'),
|
||||
require.resolve('babel-plugin-transform-object-rest-spread'),
|
||||
|
|
|
@ -2,17 +2,30 @@ import loaderUtils from 'loader-utils'
|
|||
|
||||
module.exports = function (content, sourceMap) {
|
||||
this.cacheable()
|
||||
const callback = this.async()
|
||||
const resourcePath = this.resourcePath
|
||||
|
||||
const query = loaderUtils.getOptions(this)
|
||||
|
||||
// Allows you to do checks on the file name. For example it's used to check if there's both a .js and .jsx file.
|
||||
if (query.validateFileName) {
|
||||
try {
|
||||
query.validateFileName(resourcePath)
|
||||
} catch (err) {
|
||||
callback(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const name = query.name || '[hash].[ext]'
|
||||
const context = query.context || this.options.context
|
||||
const regExp = query.regExp
|
||||
const opts = { context, content, regExp }
|
||||
const interpolatedName = loaderUtils.interpolateName(this, name, opts)
|
||||
|
||||
const interpolateName = query.interpolateName || ((name) => name)
|
||||
const interpolatedName = interpolateName(loaderUtils.interpolateName(this, name, opts), {name, opts})
|
||||
const emit = (code, map) => {
|
||||
this.emitFile(interpolatedName, code, map)
|
||||
this.callback(null, code, map)
|
||||
callback(null, code, map)
|
||||
}
|
||||
|
||||
if (query.transform) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { resolve, join, sep } from 'path'
|
||||
import { createHash } from 'crypto'
|
||||
import { realpathSync } from 'fs'
|
||||
import { realpathSync, existsSync } from 'fs'
|
||||
import webpack from 'webpack'
|
||||
import glob from 'glob-promise'
|
||||
import WriteFilePlugin from 'write-file-webpack-plugin'
|
||||
|
@ -57,11 +57,11 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
// managing pages.
|
||||
if (dev) {
|
||||
for (const p of devPages) {
|
||||
entries[join('bundles', p)] = [`./${p}?entry`]
|
||||
entries[join('bundles', p.replace('.jsx', '.js'))] = [`./${p}?entry`]
|
||||
}
|
||||
} else {
|
||||
for (const p of pages) {
|
||||
entries[join('bundles', p)] = [`./${p}?entry`]
|
||||
entries[join('bundles', p.replace('.jsx', '.js'))] = [`./${p}?entry`]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,14 +192,14 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
}
|
||||
|
||||
const rules = (dev ? [{
|
||||
test: /\.js(\?[^?]*)?$/,
|
||||
test: /\.(js|jsx)(\?[^?]*)?$/,
|
||||
loader: 'hot-self-accept-loader',
|
||||
include: [
|
||||
join(dir, 'pages'),
|
||||
nextPagesDir
|
||||
]
|
||||
}, {
|
||||
test: /\.js(\?[^?]*)?$/,
|
||||
test: /\.(js|jsx)(\?[^?]*)?$/,
|
||||
loader: 'react-hot-loader/webpack',
|
||||
exclude: /node_modules/
|
||||
}] : [])
|
||||
|
@ -207,7 +207,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
}, {
|
||||
test: /\.(js|json)(\?[^?]*)?$/,
|
||||
test: /\.(js|jsx|json)(\?[^?]*)?$/,
|
||||
loader: 'emit-file-loader',
|
||||
include: [dir, nextPagesDir],
|
||||
exclude (str) {
|
||||
|
@ -215,17 +215,34 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
},
|
||||
options: {
|
||||
name: 'dist/[path][name].[ext]',
|
||||
// We need to strip off .jsx on the server. Otherwise require without .jsx doesn't work.
|
||||
interpolateName: (name) => name.replace('.jsx', '.js'),
|
||||
validateFileName (file) {
|
||||
const cases = [{from: '.js', to: '.jsx'}, {from: '.jsx', to: '.js'}]
|
||||
|
||||
for (const item of cases) {
|
||||
const {from, to} = item
|
||||
if (file.slice(-(from.length)) !== from) {
|
||||
continue
|
||||
}
|
||||
|
||||
const filePath = file.slice(0, -(from.length)) + to
|
||||
|
||||
if (existsSync(filePath)) {
|
||||
throw new Error(`Both ${from} and ${to} file found. Please make sure you only have one of both.`)
|
||||
}
|
||||
}
|
||||
},
|
||||
// By default, our babel config does not transpile ES2015 module syntax because
|
||||
// webpack knows how to handle them. (That's how it can do tree-shaking)
|
||||
// But Node.js doesn't know how to handle them. So, we have to transpile them here.
|
||||
transform ({ content, sourceMap, interpolatedName }) {
|
||||
// Only handle .js files
|
||||
if (!(/\.js$/.test(interpolatedName))) {
|
||||
if (!(/\.(js|jsx)$/.test(interpolatedName))) {
|
||||
return { content, sourceMap }
|
||||
}
|
||||
|
||||
const babelRuntimePath = require.resolve('babel-runtime/package').replace(/[\\/]package\.json$/, '')
|
||||
|
||||
const transpiled = babelCore.transform(content, {
|
||||
babelrc: false,
|
||||
sourceMaps: dev ? 'both' : false,
|
||||
|
@ -235,6 +252,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
// That's why we need to do it here.
|
||||
// See more: https://github.com/zeit/next.js/issues/951
|
||||
plugins: [
|
||||
require.resolve(join(__dirname, './babel/plugins/remove-dotjsx-from-import.js')),
|
||||
[require.resolve('babel-plugin-transform-es2015-modules-commonjs')],
|
||||
[
|
||||
require.resolve('babel-plugin-module-resolver'),
|
||||
|
@ -291,7 +309,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
presets: [require.resolve('./babel/preset')]
|
||||
}
|
||||
}, {
|
||||
test: /\.js(\?[^?]*)?$/,
|
||||
test: /\.(js|jsx)(\?[^?]*)?$/,
|
||||
loader: 'babel-loader',
|
||||
include: [dir],
|
||||
exclude (str) {
|
||||
|
@ -321,6 +339,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet
|
|||
chunkFilename: '[name]'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.json'],
|
||||
modules: [
|
||||
nextNodeModulesDir,
|
||||
'node_modules',
|
||||
|
|
|
@ -11,7 +11,7 @@ const defaultConfig = {
|
|||
assetPrefix: '',
|
||||
configOrigin: 'default',
|
||||
useFileSystemPublicRoutes: true,
|
||||
pagesGlobPattern: 'pages/**/*.js'
|
||||
pagesGlobPattern: 'pages/**/*.+(js|jsx)'
|
||||
}
|
||||
|
||||
export default function getConfig (dir, customConfig) {
|
||||
|
|
|
@ -27,11 +27,13 @@ function getPaths (id) {
|
|||
const i = sep === '/' ? id : id.replace(/\//g, sep)
|
||||
|
||||
if (i.slice(-3) === '.js') return [i]
|
||||
if (i.slice(-4) === '.jsx') return [i]
|
||||
if (i.slice(-5) === '.json') return [i]
|
||||
|
||||
if (i[i.length - 1] === sep) {
|
||||
return [
|
||||
i + 'index.js',
|
||||
i + 'index.jsx',
|
||||
i + 'index.json'
|
||||
]
|
||||
}
|
||||
|
@ -39,6 +41,8 @@ function getPaths (id) {
|
|||
return [
|
||||
i + '.js',
|
||||
join(i, 'index.js'),
|
||||
i + '.jsx',
|
||||
join(i, 'index.jsx'),
|
||||
i + '.json',
|
||||
join(i, 'index.json')
|
||||
]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { join } from 'path'
|
||||
import { readdirSync, existsSync } from 'fs'
|
||||
|
||||
export const IS_BUNDLED_PAGE = /^bundles[/\\]pages.*\.js$/
|
||||
export const MATCH_ROUTE_NAME = /^bundles[/\\]pages[/\\](.*)\.js$/
|
||||
export const IS_BUNDLED_PAGE = /^bundles[/\\]pages.*\.(js|jsx)$/
|
||||
export const MATCH_ROUTE_NAME = /^bundles[/\\]pages[/\\](.*)\.(js|jsx)$/
|
||||
|
||||
export function getAvailableChunks (dir, dist) {
|
||||
const chunksDir = join(dir, dist, 'chunks')
|
||||
|
|
3
test/integration/basic/components/hello.jsx
Normal file
3
test/integration/basic/components/hello.jsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const Hello = () => (
|
||||
<div>Hello</div>
|
||||
)
|
3
test/integration/basic/components/world.jsx
Normal file
3
test/integration/basic/components/world.jsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const World = () => (
|
||||
<div>World</div>
|
||||
)
|
6
test/integration/basic/pages/custom-extension.jsx
Normal file
6
test/integration/basic/pages/custom-extension.jsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import {World} from '../components/world'
|
||||
import {Hello} from '../components/hello.jsx'
|
||||
|
||||
export default () => (
|
||||
<div><Hello/> <World/></div>
|
||||
)
|
|
@ -38,6 +38,12 @@ export default function ({ app }, suiteName, render, fetch) {
|
|||
expect(html).not.toContain('<link rel="stylesheet" href="dedupe-style.css" class="next-head"/><link rel="stylesheet" href="dedupe-style.css" class="next-head"/>')
|
||||
})
|
||||
|
||||
it('should render the page with custom extension', async () => {
|
||||
const html = await render('/custom-extension')
|
||||
expect(html).toContain('<div>Hello</div>')
|
||||
expect(html).toContain('<div>World</div>')
|
||||
})
|
||||
|
||||
test('renders styled jsx', async () => {
|
||||
const $ = await get$('/styled-jsx')
|
||||
const styleId = $('#blue-box').attr('class')
|
||||
|
|
Loading…
Reference in a new issue