fix(nextjs): fix Next.js schematics to support dynamic routes and remove unnecessary next plugins (as of 9.5) (#3574)
Closes #2478, #2490
This commit is contained in:
parent
26c13d66a7
commit
9f35646c65
@ -38,6 +38,14 @@ nx g component my-component --project=mylib --classComponent
|
||||
|
||||
## Options
|
||||
|
||||
### directory
|
||||
|
||||
Alias(es): d
|
||||
|
||||
Type: `string`
|
||||
|
||||
Create the page under this directory (can be nested). Will be created under 'pages/'.
|
||||
|
||||
### export
|
||||
|
||||
Alias(es): e
|
||||
|
||||
@ -38,6 +38,14 @@ nx g component my-component --project=mylib --classComponent
|
||||
|
||||
## Options
|
||||
|
||||
### directory
|
||||
|
||||
Alias(es): d
|
||||
|
||||
Type: `string`
|
||||
|
||||
Create the page under this directory (can be nested). Will be created under 'pages/'.
|
||||
|
||||
### export
|
||||
|
||||
Alias(es): e
|
||||
|
||||
@ -80,31 +80,18 @@ forEachCli('nx', () => {
|
||||
|
||||
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
|
||||
|
||||
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/react:lib ${libName} --no-interactive --style=none`
|
||||
);
|
||||
|
||||
const mainPath = `apps/${appName}/pages/index.tsx`;
|
||||
updateFile(mainPath, `import '@proj/${libName}';\n` + readFile(mainPath));
|
||||
updateFile(
|
||||
`apps/${appName}/next.config.js`,
|
||||
`
|
||||
const withCSS = require('@zeit/next-css');
|
||||
module.exports = withCSS({
|
||||
cssModules: false,
|
||||
generateBuildId: function () {
|
||||
return 'fixed';
|
||||
}
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
await checkApp(appName, {
|
||||
checkUnitTest: true,
|
||||
checkLint: true,
|
||||
checkE2E: false,
|
||||
});
|
||||
|
||||
// check that the configuration was consumed
|
||||
expect(readFile(`dist/apps/${appName}/.next/BUILD_ID`)).toEqual('fixed');
|
||||
}, 120000);
|
||||
|
||||
it('should be able to dynamically load a lib', async () => {
|
||||
@ -113,7 +100,9 @@ module.exports = withCSS({
|
||||
const libName = uniq('lib');
|
||||
|
||||
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
|
||||
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/react:lib ${libName} --no-interactive --style=none`
|
||||
);
|
||||
|
||||
const mainPath = `apps/${appName}/pages/index.tsx`;
|
||||
updateFile(
|
||||
@ -181,8 +170,8 @@ module.exports = withCSS({
|
||||
import { TestComponent } from '@proj/${tsxLibName}';\n\n
|
||||
` +
|
||||
content.replace(
|
||||
`<main>`,
|
||||
`<main>
|
||||
`</h2>`,
|
||||
`</h2>
|
||||
<div>
|
||||
{testFn()}
|
||||
<TestComponent text="Hello Next.JS" />
|
||||
|
||||
@ -166,7 +166,7 @@
|
||||
"mime": "2.4.4",
|
||||
"mini-css-extract-plugin": "0.8.0",
|
||||
"minimatch": "3.0.4",
|
||||
"next": "9.3.3",
|
||||
"next": "9.5.2",
|
||||
"ng-packagr": "9.1.0",
|
||||
"ngrx-store-freeze": "0.2.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"version": "10.1.0-beta.0",
|
||||
"packages": {
|
||||
"next": {
|
||||
"version": "9.5.1",
|
||||
"version": "9.5.2",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"node-sass": {
|
||||
|
||||
@ -40,6 +40,7 @@ function run(
|
||||
exportApp(
|
||||
root,
|
||||
{
|
||||
statusMessage: 'Exporting',
|
||||
silent: options.silent,
|
||||
threads: options.threads,
|
||||
outdir: `${buildOptions.outputPath}/exported`,
|
||||
|
||||
@ -56,7 +56,9 @@ describe('app', () => {
|
||||
{ name: 'myApp', style: 'scss' },
|
||||
appTree
|
||||
);
|
||||
expect(result.exists('apps/my-app/pages/index.scss')).toEqual(true);
|
||||
expect(result.exists('apps/my-app/pages/index.module.scss')).toEqual(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
<% if (style === 'scss') { %>
|
||||
const withSass = require('@zeit/next-sass');
|
||||
module.exports = withSass({
|
||||
// Set this to true if you use CSS modules.
|
||||
// See: https://github.com/css-modules/css-modules
|
||||
cssModules: false
|
||||
});
|
||||
<% } else if (style === 'less') { %>
|
||||
<% if (style === 'less') { %>
|
||||
const withLess = require('@zeit/next-less');
|
||||
module.exports = withLess({
|
||||
// Set this to true if you use CSS modules.
|
||||
@ -19,13 +12,6 @@ module.exports = withStylus({
|
||||
// See: https://github.com/css-modules/css-modules
|
||||
cssModules: false
|
||||
});
|
||||
<% } else if (style === 'css') { %>
|
||||
const withCSS = require('@zeit/next-css');
|
||||
module.exports = withCSS({
|
||||
// Set this to true if you use CSS modules.
|
||||
// See: https://github.com/css-modules/css-modules
|
||||
cssModules: false
|
||||
});
|
||||
<% } else if (
|
||||
style === 'styled-components'
|
||||
||style === '@emotion/styled'
|
||||
@ -34,11 +20,6 @@ module.exports = withCSS({
|
||||
) { %>
|
||||
module.exports ={};
|
||||
<% } else {
|
||||
// Defaults to CSS %>
|
||||
const withCSS = require('@zeit/next-css');
|
||||
module.exports = withCSS({
|
||||
// Set this to true if you use CSS modules.
|
||||
// See: https://github.com/css-modules/css-modules
|
||||
cssModules: false
|
||||
});
|
||||
// Defaults to CSS/SASS (which don't need plugin as of Next 9.3) %>
|
||||
module.exports ={};
|
||||
<% } %>
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
.page {
|
||||
}
|
||||
@ -1,16 +1,15 @@
|
||||
import React from 'react';
|
||||
<% if (styledModule && styledModule !== 'styled-jsx') {
|
||||
var wrapper = 'StyledApp';
|
||||
var wrapper = 'StyledPage';
|
||||
%>import styled from '<%= styledModule %>';<% } else {
|
||||
var wrapper = 'div';
|
||||
%>
|
||||
<%= style !== 'styled-jsx' ? `import './${fileName}.${style}';` : '' %>
|
||||
<%= style !== 'styled-jsx' ? `import styles from './${fileName}.module.${style}';` : '' %>
|
||||
<% }
|
||||
%>
|
||||
import { ReactComponent as NxLogo } from '../public/nx-logo-white.svg';
|
||||
|
||||
<% if (styledModule && styledModule !== 'styled-jsx') { %>
|
||||
const StyledApp = styled.div`<%= styleContent %>`;
|
||||
const StyledPage = styled.div`<%= styleContent %>`;
|
||||
<% }%>
|
||||
|
||||
export const Index = () => {
|
||||
@ -20,7 +19,7 @@ export const Index = () => {
|
||||
* Note: The corresponding styles are in the ./${fileName}.${style} file.
|
||||
*/
|
||||
return (
|
||||
<<%= wrapper %><% if (!styledModule || styledModule === 'styled-jsx') {%> className="app"<% } %>>
|
||||
<<%= wrapper %><% if (!styledModule || styledModule === 'styled-jsx') {%> className={styles.page}<% } %>>
|
||||
<%= styledModule === 'styled-jsx' ? `<style jsx>{\`${styleContent}\`}</style>` : `` %>
|
||||
<%= appContent %>
|
||||
</<%= wrapper %>>
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { ReactComponent as NxLogo } from '../public/nx-logo-white.svg';
|
||||
import './styles.css';
|
||||
|
||||
const CustomApp = ({ Component, pageProps }: AppProps) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Welcome to <%= name %>!</title>
|
||||
</Head>
|
||||
<div className="app">
|
||||
<header className="flex">
|
||||
<NxLogo width="75" height="50" />
|
||||
<h1>Welcome to <%= name %>!</h1>
|
||||
</header>
|
||||
<main>
|
||||
<Component {...pageProps}/>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomApp;
|
||||
@ -19,7 +19,6 @@ export default class CustomDocument extends Document<{ styleTags: ReactElement[]
|
||||
return (
|
||||
<html>
|
||||
<Head>
|
||||
<title>Welcome to <%= name %>!</title>
|
||||
{this.props.styleTags}
|
||||
</Head>
|
||||
<body>
|
||||
|
||||
@ -8,9 +8,4 @@ describe('Index', () => {
|
||||
const { baseElement } = render(<Index />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have a greeting as the title', () => {
|
||||
const { getByText } = render(<Index />);
|
||||
expect(getByText('Welcome to <%= projectName %>!')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
export function createAppJsx(name: string) {
|
||||
return `
|
||||
<header className="flex">
|
||||
<NxLogo alt="" width="75" height="50" />
|
||||
<h1>Welcome to ${name}!</h1>
|
||||
</header>
|
||||
<main>
|
||||
<h2>Resources & Tools</h2>
|
||||
<p>
|
||||
Thank you for using and showing some ♥ for Nx.
|
||||
@ -86,80 +81,60 @@ nx affected:test
|
||||
nx affected:e2e
|
||||
\`}</pre>
|
||||
</details>
|
||||
</main>
|
||||
`;
|
||||
}
|
||||
|
||||
interface CreateStyleRulesOptions {
|
||||
isUsingJsxBasedSolution: boolean;
|
||||
createHostBlock: boolean;
|
||||
}
|
||||
|
||||
export function createStyleRules({
|
||||
isUsingJsxBasedSolution,
|
||||
createHostBlock,
|
||||
}: CreateStyleRulesOptions) {
|
||||
const childSelectorPrefix = isUsingJsxBasedSolution ? '' : '.app';
|
||||
return `${
|
||||
createHostBlock
|
||||
? `
|
||||
export function createStyleRules() {
|
||||
return `
|
||||
.app {
|
||||
font-family: sans-serif;
|
||||
min-width: 300px;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
}
|
||||
`
|
||||
: `
|
||||
font-family: sans-serif;
|
||||
min-width: 300px;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
`
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .gutter-left {
|
||||
.app .gutter-left {
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .col-span-2 {
|
||||
.app .col-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .flex {
|
||||
.app .flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} header {
|
||||
.app header {
|
||||
background-color: #143055;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} main {
|
||||
.app main {
|
||||
padding: 0 36px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} p {
|
||||
.app p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} h1 {
|
||||
.app h1 {
|
||||
text-align: center;
|
||||
margin-left: 18px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} h2 {
|
||||
.app h2 {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
margin: 40px 0 10px 0;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .resources {
|
||||
.app .resources {
|
||||
text-align: center;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@ -168,7 +143,7 @@ ${childSelectorPrefix} .resources {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .resource {
|
||||
.app .resource {
|
||||
color: #0094ba;
|
||||
height: 36px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
@ -178,18 +153,18 @@ ${childSelectorPrefix} .resource {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .resource:hover {
|
||||
.app .resource:hover {
|
||||
background-color: rgba(68, 138, 255, 0.04);
|
||||
}
|
||||
|
||||
${childSelectorPrefix} pre {
|
||||
.app pre {
|
||||
padding: 9px;
|
||||
border-radius: 4px;
|
||||
background-color: black;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} details {
|
||||
.app details {
|
||||
border-radius: 4px;
|
||||
color: #333;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
@ -198,25 +173,25 @@ ${childSelectorPrefix} details {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} summary {
|
||||
.app summary {
|
||||
outline: none;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .github-star-container {
|
||||
.app .github-star-container {
|
||||
margin-top: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .github-star-container a {
|
||||
.app .github-star-container a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .github-star-badge {
|
||||
.app .github-star-badge {
|
||||
color: #24292e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -229,12 +204,12 @@ ${childSelectorPrefix} .github-star-badge {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
${childSelectorPrefix} .github-star-badge:hover {
|
||||
.app .github-star-badge:hover {
|
||||
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
|
||||
border-color: rgba(27,31,35,.35);
|
||||
background-position: -.5em;
|
||||
}
|
||||
${childSelectorPrefix} .github-star-badge .material-icons {
|
||||
.app .github-star-badge .material-icons {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 4px;
|
||||
|
||||
@ -24,11 +24,7 @@ export function createApplicationFiles(options: NormalizedSchema): Rule {
|
||||
tmpl: '',
|
||||
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
|
||||
appContent: createAppJsx(options.name),
|
||||
styleContent: createStyleRules({
|
||||
isUsingJsxBasedSolution: !!options.styledModule,
|
||||
createHostBlock:
|
||||
!options.styledModule || options.styledModule === 'styled-jsx',
|
||||
}),
|
||||
styleContent: createStyleRules(),
|
||||
}),
|
||||
options.styledModule
|
||||
? filter((file) => !file.endsWith(`.${options.style}`))
|
||||
|
||||
@ -32,6 +32,11 @@
|
||||
},
|
||||
"x-prompt": "What name would you like to use for the component?"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "Create the component under this directory (can be nested).",
|
||||
"alias": "d"
|
||||
},
|
||||
"style": {
|
||||
"description": "The file extension to be used for style files.",
|
||||
"type": "string",
|
||||
|
||||
@ -23,4 +23,24 @@ describe('component', () => {
|
||||
expect(tree.exists('apps/my-app/src/pages/hello.tsx')).toBeTruthy();
|
||||
expect(tree.exists('apps/my-app/src/pages/hello.css')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support dynamic routes and directories', async () => {
|
||||
const tree = await runSchematic(
|
||||
'page',
|
||||
{ name: '[dynamic]', directory: 'posts', project: projectName },
|
||||
appTree
|
||||
);
|
||||
|
||||
expect(
|
||||
tree.exists('apps/my-app/src/pages/posts/[dynamic].tsx')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
tree.exists('apps/my-app/src/pages/posts/[dynamic].css')
|
||||
).toBeTruthy();
|
||||
|
||||
const content = tree
|
||||
.read('apps/my-app/src/pages/posts/[dynamic].tsx')
|
||||
.toString();
|
||||
expect(content).toMatch(/DynamicProps/);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';
|
||||
import { addStyleDependencies } from '../../utils/styles';
|
||||
|
||||
interface Schema {
|
||||
name: string;
|
||||
project: string;
|
||||
style: string;
|
||||
withTests?: boolean;
|
||||
}
|
||||
import { Schema } from './schema';
|
||||
|
||||
/*
|
||||
* This schematic is basically the React component one, but for Next we need
|
||||
@ -17,7 +11,7 @@ export default function (options: Schema): Rule {
|
||||
return chain([
|
||||
externalSchematic('@nrwl/react', 'component', {
|
||||
...options,
|
||||
directory: 'pages', // This HAS to be here or Next won't work!
|
||||
directory: options.directory ? `pages/${options.directory}` : 'pages',
|
||||
pascalCaseFiles: false,
|
||||
export: false,
|
||||
classComponent: false,
|
||||
|
||||
9
packages/next/src/schematics/page/schema.d.ts
vendored
Normal file
9
packages/next/src/schematics/page/schema.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
export interface Schema {
|
||||
name: string;
|
||||
directory: string;
|
||||
fileName: string;
|
||||
project: string;
|
||||
style: string;
|
||||
withTests?: boolean;
|
||||
js?: boolean;
|
||||
}
|
||||
@ -32,6 +32,11 @@
|
||||
},
|
||||
"x-prompt": "What name would you like to use for the component?"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "Create the page under this directory (can be nested). Will be created under 'pages/'.",
|
||||
"alias": "d"
|
||||
},
|
||||
"style": {
|
||||
"description": "The file extension to be used for style files.",
|
||||
"type": "string",
|
||||
|
||||
@ -4,11 +4,9 @@ import { CSS_IN_JS_DEPENDENCIES } from '@nrwl/react';
|
||||
import {
|
||||
babelPluginStyledComponentsVersion,
|
||||
emotionServerVersion,
|
||||
zeitNextCss,
|
||||
zeitNextLess,
|
||||
zeitNextSass,
|
||||
zeitNextStylus,
|
||||
nodeSass,
|
||||
zeitNextLess,
|
||||
zeitNextStylus,
|
||||
} from './versions';
|
||||
|
||||
export const NEXT_SPECIFIC_STYLE_DEPENDENCIES = {
|
||||
@ -27,15 +25,11 @@ export const NEXT_SPECIFIC_STYLE_DEPENDENCIES = {
|
||||
devDependencies: CSS_IN_JS_DEPENDENCIES['@emotion/styled'].devDependencies,
|
||||
},
|
||||
css: {
|
||||
dependencies: {
|
||||
'@zeit/next-css': zeitNextCss,
|
||||
},
|
||||
dependencies: {},
|
||||
devDependencies: {},
|
||||
},
|
||||
scss: {
|
||||
dependencies: {
|
||||
'@zeit/next-sass': zeitNextSass,
|
||||
},
|
||||
dependencies: {},
|
||||
devDependencies: {
|
||||
'node-sass': nodeSass,
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export const nxVersion = '*';
|
||||
|
||||
export const nextVersion = '9.5.1';
|
||||
export const nextVersion = '9.5.2';
|
||||
export const zeitNextCss = '1.0.1';
|
||||
export const zeitNextSass = '1.0.1';
|
||||
export const nodeSass = '4.14.1';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { schema } from '@angular-devkit/core';
|
||||
import { fileSync } from 'tmp';
|
||||
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
||||
import { readFileSync, unlinkSync, writeFileSync } from 'fs';
|
||||
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
|
||||
import { Architect } from '@angular-devkit/architect';
|
||||
import { join } from 'path';
|
||||
@ -89,7 +89,6 @@ describe('Command Runner Builder', () => {
|
||||
//wait a tick for the serial runner to schedule the first task
|
||||
await Promise.resolve();
|
||||
const run = await scheduleRun;
|
||||
const result = await run.result;
|
||||
|
||||
expect(exec).toHaveBeenCalledWith(`echo --a=123 --b=456`, {
|
||||
stdio: [0, 1, 2],
|
||||
|
||||
27
packages/workspace/src/utils/name-utils.spec.ts
Normal file
27
packages/workspace/src/utils/name-utils.spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import * as utils from './name-utils';
|
||||
|
||||
describe('name-utils', () => {
|
||||
it('should support class names', () => {
|
||||
expect(utils.toClassName('foo-bar')).toEqual('FooBar');
|
||||
expect(utils.toClassName('foo_bar')).toEqual('FooBar');
|
||||
expect(utils.toClassName('fooBar')).toEqual('FooBar');
|
||||
expect(utils.toClassName('[fooBar]')).toEqual('FooBar');
|
||||
expect(utils.toClassName('[...fooBar]')).toEqual('FooBar');
|
||||
});
|
||||
|
||||
it('should support property names', () => {
|
||||
expect(utils.toPropertyName('foo-bar')).toEqual('fooBar');
|
||||
expect(utils.toPropertyName('foo_bar')).toEqual('fooBar');
|
||||
expect(utils.toPropertyName('FooBar')).toEqual('fooBar');
|
||||
expect(utils.toPropertyName('[fooBar]')).toEqual('fooBar');
|
||||
expect(utils.toPropertyName('[...fooBar]')).toEqual('fooBar');
|
||||
});
|
||||
|
||||
it('should support file names', () => {
|
||||
expect(utils.toFileName('foo-bar')).toEqual('foo-bar');
|
||||
expect(utils.toFileName('foo_bar')).toEqual('foo-bar');
|
||||
expect(utils.toFileName('FooBar')).toEqual('foo-bar');
|
||||
expect(utils.toFileName('[fooBar]')).toEqual('[foo-bar]');
|
||||
expect(utils.toFileName('[...fooBar]')).toEqual('[...foo-bar]');
|
||||
});
|
||||
});
|
||||
@ -34,6 +34,7 @@ export function toPropertyName(s: string): string {
|
||||
.replace(/(-|_|\.|\s)+(.)?/g, (_, __, chr) =>
|
||||
chr ? chr.toUpperCase() : ''
|
||||
)
|
||||
.replace(/[^a-zA-Z\d]/g, '')
|
||||
.replace(/^([A-Z])/, (m) => m.toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user