├── .eslintignore
├── .npmrc
├── tsconfig.json
├── playground
├── package.json
├── components
│ ├── icon-card.vue
│ ├── header-menu.vue
│ ├── table-card.vue
│ ├── tree-card.vue
│ ├── other-card.vue
│ ├── navigation-card.vue
│ ├── data-card.vue
│ └── form-card.vue
├── utils.ts
├── nuxt.config.ts
└── app.vue
├── .eslintrc
├── .nuxtrc
├── .editorconfig
├── src
├── core
│ ├── index.ts
│ ├── cache.ts
│ ├── directives.ts
│ ├── themes.ts
│ ├── injection.ts
│ ├── methods.ts
│ ├── styles.ts
│ ├── globalConfig.ts
│ ├── localePlugn.ts
│ ├── teleports.ts
│ ├── imports.ts
│ ├── components.ts
│ ├── options.ts
│ ├── themeChalk.ts
│ └── transformPlugin.ts
├── utils.ts
├── module.ts
├── config.ts
└── types.ts
├── .vscode
└── launch.json
├── .gitignore
├── .github
└── workflows
│ ├── unit-test.yml
│ └── release.yml
├── LICENSE
├── package.json
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 | node-linker=hoisted
3 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./playground/.nuxt/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "playground"
4 | }
5 |
--------------------------------------------------------------------------------
/playground/components/icon-card.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@nuxtjs/eslint-config-typescript"
4 | ],
5 | "rules": {
6 | "@typescript-eslint/no-unused-vars": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/playground/utils.ts:
--------------------------------------------------------------------------------
1 | import type { MessageParams } from 'element-plus'
2 |
3 | export function $message (options?: MessageParams) {
4 | return ElMessage(options)
5 | }
6 |
--------------------------------------------------------------------------------
/.nuxtrc:
--------------------------------------------------------------------------------
1 | imports.autoImport=true
2 | typescript.includeWorkspace=true
3 |
4 | # enable TypeScript bundler module resolution - https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler
5 | experimental.typescriptBundlerResolution=true
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 2
5 | indent_style = space
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './cache'
2 | export * from './components'
3 | export * from './directives'
4 | export * from './globalConfig'
5 | export * from './imports'
6 | export * from './injection'
7 | export * from './localePlugn'
8 | export * from './methods'
9 | export * from './options'
10 | export * from './styles'
11 | export * from './teleports'
12 | export * from './themeChalk'
13 | export * from './themes'
14 | export * from './transformPlugin'
15 |
--------------------------------------------------------------------------------
/src/core/cache.ts:
--------------------------------------------------------------------------------
1 | import { libraryName } from '../config'
2 | import type { ModuleOptions } from '../types'
3 | import { camelize } from '../utils'
4 |
5 | export function resolveCache (config: ModuleOptions) {
6 | const { defaultLocale } = config
7 | const locale = camelize(defaultLocale)
8 |
9 | return {
10 | filename: `${libraryName}-cache.mjs`,
11 | getContents: () => {
12 | return `export * from '${libraryName}';
13 | ${defaultLocale !== 'en'
14 | ? `import ${locale} from '${libraryName}/es/locale/lang/${defaultLocale}.mjs';
15 | export { ${locale} };`
16 | : ''}`
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/core/directives.ts:
--------------------------------------------------------------------------------
1 | import { toArray } from '../utils'
2 | import type { ModuleOptions } from '../types'
3 | import { getComponentPath, getStyleDir } from './index'
4 |
5 | export function resolveDirectives (
6 | config: ModuleOptions,
7 | name: string
8 | ): undefined | [name: string, path: string, style?: string] {
9 | const { directives } = config
10 |
11 | if (!directives[name]) { return undefined }
12 |
13 | const [directive, styleName] = toArray(directives[name])
14 | const path = getComponentPath(styleName ?? directive)
15 | const style = styleName && getStyleDir(config, styleName)
16 |
17 | return [directive, path, style]
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/themes.ts:
--------------------------------------------------------------------------------
1 | import { useNuxt } from '@nuxt/kit'
2 | import { libraryName } from '../config'
3 | import { resolvePath } from '../utils'
4 | import type { ModuleOptions } from '../types'
5 |
6 | export function resolveThemes (config: ModuleOptions) {
7 | const nuxt = useNuxt()
8 | const { themes, importStyle } = config
9 | const allThemes = new Set(themes)
10 |
11 | if (importStyle === false) {
12 | return
13 | }
14 |
15 | allThemes.forEach(async (item) => {
16 | const isScss = importStyle === 'scss'
17 | const theme = await resolvePath(`${libraryName}/theme-chalk${isScss ? '/src' : ''}/${item}/css-vars.${isScss ? 'scss' : 'css'}`)
18 |
19 | nuxt.options.css.push(theme)
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch Chrome",
6 | "request": "launch",
7 | "type": "chrome",
8 | "url": "http://localhost:3000",
9 | "webRoot": "${workspaceFolder}"
10 | },
11 | {
12 | "name": "Launch Edge",
13 | "request": "launch",
14 | "type": "msedge",
15 | "url": "http://localhost:3000",
16 | "webRoot": "${workspaceFolder}"
17 | },
18 | {
19 | "name": "Run and debug",
20 | "type": "node",
21 | "request": "launch",
22 | "cwd": "${workspaceFolder}",
23 | "runtimeExecutable": "pnpm",
24 | "runtimeArgs": ["dev", "--preserve-symlinks"]
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/core/injection.ts:
--------------------------------------------------------------------------------
1 | import { libraryName } from '../config'
2 | import type { ModuleOptions } from '../types'
3 |
4 | /** Inject some additional configuration into Vue at runtime */
5 | export function resolveInjection (config: ModuleOptions) {
6 | const { injectionID, injectionZIndex } = config
7 |
8 | return {
9 | filename: `${libraryName}-injection.plugin.mjs`,
10 | getContents: () => {
11 | return `import { defineNuxtPlugin, ID_INJECTION_KEY, ZINDEX_INJECTION_KEY } from '#imports';
12 |
13 | export default defineNuxtPlugin(nuxtApp => {
14 | nuxtApp.vueApp
15 | .provide(ID_INJECTION_KEY, ${JSON.stringify(injectionID)})
16 | .provide(ZINDEX_INJECTION_KEY, ${JSON.stringify(injectionZIndex)});
17 | })
18 | `
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules
3 |
4 | # Logs
5 | *.log*
6 |
7 | # Temp directories
8 | .temp
9 | .tmp
10 | .cache
11 |
12 | # Yarn
13 | **/.yarn/cache
14 | **/.yarn/*state*
15 |
16 | # Generated dirs
17 | dist
18 |
19 | # Nuxt
20 | .nuxt
21 | .output
22 | .vercel_build_output
23 | .build-*
24 | .env
25 | .netlify
26 |
27 | # Env
28 | .env
29 |
30 | # Testing
31 | reports
32 | coverage
33 | *.lcov
34 | .nyc_output
35 |
36 | # VSCode
37 | .vscode/*
38 | !.vscode/settings.json
39 | !.vscode/tasks.json
40 | !.vscode/launch.json
41 | !.vscode/extensions.json
42 | !.vscode/*.code-snippets
43 |
44 | # Intellij idea
45 | *.iml
46 | .idea
47 |
48 | # OSX
49 | .DS_Store
50 | .AppleDouble
51 | .LSOverride
52 | .AppleDB
53 | .AppleDesktop
54 | Network Trash Folder
55 | Temporary Items
56 | .apdisk
57 |
58 | package-lock.json
59 | yarn.lock
60 |
--------------------------------------------------------------------------------
/src/core/methods.ts:
--------------------------------------------------------------------------------
1 | import { libraryName } from '../config'
2 | import type { ModuleOptions } from '../types'
3 |
4 | /**
5 | * NOTE: Due to some situations where the value cannot be obtained when using getCurrentInstance in the setup function, it is currently not possible to automatically inject the context.
6 | * Now let the user configure it first, and then look for a more suitable way in the future.
7 | */
8 | export function resolveMethods (config: ModuleOptions) {
9 | const { installMethods } = config
10 |
11 | return {
12 | filename: `${libraryName}-methods.plugin.mjs`,
13 | getContents: () => {
14 | return `import { defineNuxtPlugin, ${installMethods.join(',')} } from '#imports';
15 |
16 | export default defineNuxtPlugin(nuxtApp => {
17 | ${installMethods.reduce((all, name) => `${all}.use(${name})`, 'nuxtApp.vueApp')};
18 | })
19 | `
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/core/styles.ts:
--------------------------------------------------------------------------------
1 | import { allMethods, libraryName } from '../config'
2 | import { hyphenate } from '../utils'
3 | import type { ModuleOptions } from '../types'
4 |
5 | export function getStyleDir (config: ModuleOptions, name: string) {
6 | if (config.importStyle === false) {
7 | return undefined
8 | }
9 | const dir = hyphenate(name.slice(2))
10 | const type = config.importStyle === 'scss' ? 'index' : 'css'
11 |
12 | return `${libraryName}/es/components/${dir}/style/${type}.mjs`
13 | }
14 |
15 | export function resolveStyles (config: ModuleOptions, name: string) {
16 | const { components, noStylesComponents } = config
17 | const allComponents = [...components, ...allMethods]
18 |
19 | if (!allComponents.includes(name) || noStylesComponents.includes(name)) {
20 | return undefined
21 | }
22 |
23 | return /^El[A-Z]/.test(name) ? getStyleDir(config, name) : undefined
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/unit-test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Test
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 |
7 | jobs:
8 | test:
9 | strategy:
10 | matrix:
11 | os: [ubuntu-latest, windows-latest]
12 | node-version: [18, 20]
13 | fail-fast: false
14 |
15 | runs-on: ${{ matrix.os }}
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | - uses: pnpm/action-setup@v4
21 | name: Install pnpm
22 | with:
23 | version: latest
24 |
25 | - uses: actions/setup-node@v4
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: pnpm
29 |
30 | - name: Install
31 | run: pnpm i --frozen-lockfile
32 |
33 | - name: Build
34 | run: pnpm build
35 |
36 | - name: Lint
37 | run: pnpm lint:test
38 |
39 | - name: Build playground
40 | run: pnpm dev:build
41 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v**'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | with:
14 | fetch-depth: 0
15 |
16 | - name: Setup node
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: latest
20 | registry-url: https://registry.npmjs.org/
21 |
22 | - name: Setup pnpm
23 | uses: pnpm/action-setup@v4
24 | with:
25 | version: latest
26 |
27 | - name: Install
28 | run: pnpm i --frozen-lockfile
29 |
30 | - name: Build
31 | run: pnpm build
32 |
33 | - name: Publish
34 | run: pnpm publish --access public --no-git-checks
35 | env:
36 | NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH_TOKEN}}
37 |
38 | - run: npx changelogithub
39 | env:
40 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
41 |
--------------------------------------------------------------------------------
/src/core/globalConfig.ts:
--------------------------------------------------------------------------------
1 | import { libraryName } from '../config'
2 | import type { ModuleOptions } from '../types'
3 | import { camelize, resolveComponentPath } from '../utils'
4 |
5 | export function resolveGlobalConfig (config: ModuleOptions) {
6 | const { globalConfig, cache, defaultLocale } = config
7 | const needLocale = !!(cache && defaultLocale !== 'en')
8 | const locale = camelize(defaultLocale)
9 | let provideConfig = JSON.stringify(globalConfig)
10 |
11 | if (needLocale) {
12 | provideConfig = JSON.stringify(Object.assign({}, globalConfig, { locale })).replace(`"${locale}"`, locale)
13 | }
14 |
15 | return {
16 | filename: `${libraryName}-globalConfig.plugin.mjs`,
17 | getContents: async () => {
18 | return `import { defineNuxtPlugin, provideGlobalConfig } from '#imports';
19 | ${needLocale ? `import { ${locale} } from '${await resolveComponentPath('', cache)}';\n` : ''}
20 | export default defineNuxtPlugin(nuxtApp => {
21 | provideGlobalConfig(${provideConfig}, nuxtApp.vueApp, true);
22 | })`
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/core/localePlugn.ts:
--------------------------------------------------------------------------------
1 | import { createUnplugin } from 'unplugin'
2 | import MagicString from 'magic-string'
3 | import type { NuxtOptions } from '@nuxt/schema'
4 | import { libraryName } from '../config'
5 |
6 | interface LocalePluginOptions {
7 | sourcemap?: NuxtOptions['sourcemap']['client']
8 | locale?: string
9 | }
10 |
11 | export const localePlugin = createUnplugin((options: LocalePluginOptions) => {
12 | return {
13 | name: `${libraryName}:locale`,
14 | enforce: 'pre',
15 | transformInclude (id) {
16 | const regExp = new RegExp(`${libraryName}/es/hooks/use-locale/index`)
17 | return !!id.match(regExp)
18 | },
19 | transform (code, id) {
20 | const s = new MagicString(code)
21 |
22 | s.replace('/locale/lang/en', `/locale/lang/${options.locale}`)
23 |
24 | if (s.hasChanged()) {
25 | return {
26 | code: s.toString(),
27 | map: options.sourcemap
28 | ? s.generateMap({ source: id, includeContent: true })
29 | : undefined
30 | }
31 | }
32 | }
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/src/core/teleports.ts:
--------------------------------------------------------------------------------
1 | import { libraryName } from '../config'
2 | import type { ModuleOptions } from '../types'
3 |
4 | export function resolveTeleports (config: ModuleOptions) {
5 | const { namespace, appendTo } = config
6 | const defaultId = `#${namespace}-popper-container-`
7 |
8 | return {
9 | filename: `${libraryName}-teleports.plugin.mjs`,
10 | getContents: () => {
11 | return `import { defineNuxtPlugin } from '#imports'
12 |
13 | export default defineNuxtPlugin((nuxtApp) => {
14 | nuxtApp.hook('app:rendered', (ctx) => {
15 | if (ctx.ssrContext?.teleports) {
16 | ctx.ssrContext.teleports = renderTeleports(ctx.ssrContext.teleports)
17 | }
18 | })
19 | })
20 |
21 | function renderTeleports (teleports) {
22 | const body = Object.entries(teleports).reduce((all, [key, value]) => {
23 | if (key.startsWith('${defaultId}') || ${JSON.stringify(appendTo)}.includes(key)) {
24 | return \`\${all}
\${value}
\`
25 | }
26 | return all
27 | }, teleports.body || '')
28 | return { ...teleports, body }
29 | }
30 | `
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-PRESENT Element Plus
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/playground/nuxt.config.ts:
--------------------------------------------------------------------------------
1 | import { defineNuxtConfig } from 'nuxt/config'
2 | import ElementPlus from '../src/module'
3 |
4 | export default defineNuxtConfig({
5 | devtools: { enabled: true },
6 | compatibilityDate: '2024-07-05',
7 | modules: [ElementPlus],
8 | elementPlus: {
9 | cache: true,
10 | importStyle: 'scss',
11 | defaultLocale: 'zh-cn',
12 | imports: [
13 | ['useLocale', 'es/hooks/use-locale/index.mjs']
14 | ],
15 | themes: ['dark'],
16 | namespace: 'ep',
17 | injectionID: { prefix: 100, current: 1 },
18 | globalConfig: { size: 'small', zIndex: 1000, namespace: 'ep' },
19 | themeChalk: {
20 | $colors: {
21 | primary: { base: 'rgba(107,33,168, 1)' },
22 | success: { base: 'green' },
23 | warning: { base: '#f9a23c' },
24 | danger: { base: '#ff3300' },
25 | error: { base: '#f56c6c' }
26 | },
27 | dark: {
28 | $colors: {
29 | primary: { base: 'rgb(242, 216, 22)' }
30 | },
31 | '$bg-color': {
32 | page: '#0a0a0a',
33 | overlay: '#1d1e1f'
34 | }
35 | }
36 | }
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/src/core/imports.ts:
--------------------------------------------------------------------------------
1 | import { addImportsSources } from '@nuxt/kit'
2 | import { allMethods, iconLibraryName, libraryName } from '../config'
3 | import { genIconPresets, resolveComponentPath, resolvePath, toArray } from '../utils'
4 | import type { ModuleOptions, PresetImport } from '../types'
5 | import { getComponentPath } from './index'
6 |
7 | function _resolveImports (imports: Set, cache: boolean | undefined) {
8 | imports.forEach(async ([name, path]) => {
9 | addImportsSources({
10 | from: await resolveComponentPath(path, cache),
11 | imports: toArray(name)
12 | })
13 | })
14 | }
15 |
16 | export async function resolveImports (config: ModuleOptions) {
17 | const { imports, icon, cache } = config
18 | const icons = icon !== false ? genIconPresets(icon) : []
19 | const allImports = new Set(imports)
20 | const allIcons = new Set(icons)
21 |
22 | _resolveImports(allImports, cache)
23 |
24 | addImportsSources({
25 | from: await resolvePath(iconLibraryName),
26 | imports: [...allIcons]
27 | })
28 | }
29 |
30 | export function resolveBaseImports (config: ModuleOptions) {
31 | const { baseImports, cache } = config
32 | const methodImports = allMethods.map((name) => {
33 | return [name, getComponentPath(name)] as PresetImport
34 | })
35 | const allBaseImports = new Set([...baseImports, ...methodImports])
36 |
37 | _resolveImports(allBaseImports, cache)
38 | }
39 |
--------------------------------------------------------------------------------
/src/core/components.ts:
--------------------------------------------------------------------------------
1 | import { addComponent } from '@nuxt/kit'
2 | import { iconLibraryName } from '../config'
3 | import { genIconPresets, toArray, resolvePath, hyphenate, resolveComponentPath } from '../utils'
4 | import type { ModuleOptions } from '../types'
5 |
6 | export function getComponentPath (name: string): string {
7 | const dir = hyphenate(name.slice(2))
8 | return `es/components/${dir}/index.mjs`
9 | }
10 |
11 | export function resolveComponents (config: ModuleOptions) {
12 | const { components, subComponents, icon, cache } = config
13 | const icons = icon !== false ? genIconPresets(icon, iconLibraryName) : []
14 | const allComponents = new Set([...components, ...icons])
15 | const subComponentsMap = Object.fromEntries(
16 | Object.entries(subComponents).reduce((all, [key, values]) => {
17 | values.forEach((item) => {
18 | all.push([item, key])
19 | })
20 | return all
21 | }, [] as unknown as [string, any])
22 | )
23 |
24 | allComponents.forEach(async (item) => {
25 | const [name, alias, from] = toArray(item)
26 | const componentName = subComponentsMap[name] || name
27 | const filePath = from !== iconLibraryName
28 | ? await resolveComponentPath(getComponentPath(componentName), cache)
29 | : await resolvePath(from)
30 |
31 | addComponent({
32 | export: name,
33 | name: alias || name,
34 | filePath
35 | })
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/playground/components/header-menu.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
53 |
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@element-plus/nuxt",
3 | "version": "1.1.4",
4 | "description": "Element Plus module for Nuxt",
5 | "license": "MIT",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "types": "./dist/types.d.ts",
10 | "import": "./dist/module.mjs",
11 | "require": "./dist/module.cjs"
12 | }
13 | },
14 | "main": "./dist/module.cjs",
15 | "types": "./dist/types.d.ts",
16 | "files": [
17 | "dist"
18 | ],
19 | "keywords": [
20 | "element-plus",
21 | "nuxt"
22 | ],
23 | "homepage": "https://github.com/element-plus/element-plus-nuxt#readme",
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/element-plus/element-plus-nuxt.git"
27 | },
28 | "bugs": {
29 | "url": "https://github.com/element-plus/element-plus-nuxt/issues"
30 | },
31 | "scripts": {
32 | "build": "npm run dev:prepare && nuxt-module-build build",
33 | "dev": "nuxi dev playground",
34 | "dev:build": "nuxi build playground",
35 | "dev:start": "node playground/.output/server/index.mjs",
36 | "dev:prepare": "nuxt-module-build build --stub && nuxi prepare playground",
37 | "lint": "eslint . --fix --ext .ts,.vue,.js",
38 | "lint:test": "eslint . --ext .ts,.vue,.js --max-warnings 0"
39 | },
40 | "peerDependencies": {
41 | "@element-plus/icons-vue": ">=0.2.6",
42 | "element-plus": ">=2"
43 | },
44 | "dependencies": {
45 | "@nuxt/kit": "^3.13.2",
46 | "magic-string": "^0.27.0",
47 | "unplugin": "^1.15.0"
48 | },
49 | "devDependencies": {
50 | "@element-plus/icons-vue": "^2.3.1",
51 | "@nuxt/module-builder": "^0.5.5",
52 | "@nuxt/schema": "^3.13.2",
53 | "@nuxtjs/eslint-config-typescript": "^12.1.0",
54 | "@types/node": "^22.8.6",
55 | "element-plus": "^2.10.1",
56 | "eslint": "^8.57.1",
57 | "nuxi": "^3.15.0",
58 | "nuxt": "^3.13.2",
59 | "sass": "^1.89.2",
60 | "typescript": "^5.6.3",
61 | "vue": "^3.5.12"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/playground/components/table-card.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
59 |
60 |
61 |
62 |
63 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/playground/components/tree-card.vue:
--------------------------------------------------------------------------------
1 |
101 |
102 |
103 |
104 |
108 |
109 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/core/options.ts:
--------------------------------------------------------------------------------
1 | import { useNuxt } from '@nuxt/kit'
2 | import { libraryName, optimizeDeps } from '../config'
3 | import { isFunction } from '../utils'
4 | import type { ModuleOptions, Themes } from '../types'
5 |
6 | export function resolveOptions (config: ModuleOptions) {
7 | const { cache, importStyle, namespace, themeChalk } = config
8 | const nuxt = useNuxt()
9 |
10 | nuxt.options.build.transpile.push(libraryName)
11 | nuxt.options.vite.optimizeDeps ||= {}
12 | nuxt.options.vite.optimizeDeps.include ||= []
13 | nuxt.options.vite.optimizeDeps.include.push(...optimizeDeps)
14 |
15 | if (cache) {
16 | nuxt.options.vite.optimizeDeps.include.push(libraryName)
17 | } else {
18 | nuxt.options.vite.optimizeDeps.exclude ||= []
19 | nuxt.options.vite.optimizeDeps.exclude.push(libraryName)
20 | }
21 |
22 | nuxt.options.vite.css ||= {}
23 | nuxt.options.vite.css.preprocessorOptions ||= {}
24 | nuxt.options.vite.css.preprocessorOptions.scss ||= {}
25 | nuxt.options.vite.css.preprocessorOptions.scss.api ??= 'modern-compiler'
26 | nuxt.options.webpack.loaders.scss.api ??= 'modern-compiler'
27 |
28 | if (importStyle === 'scss' && themeChalk) {
29 | const files: Array<'namespace' | 'common' | Themes> = []
30 | const keys = Object.keys(themeChalk) as (keyof typeof themeChalk)[]
31 | const themes = keys.filter((key) => {
32 | return !key.startsWith('$') && themeChalk[key] && Object.keys(themeChalk[key]).length
33 | }) as Themes[]
34 |
35 | if (namespace && namespace !== 'el') {
36 | files.push('namespace')
37 | }
38 | if (keys.some(key => key.startsWith('$'))) {
39 | files.push('common')
40 | }
41 | files.push(...themes)
42 |
43 | const additionalData = files.reduce((all, item) => {
44 | all += `@use "${nuxt.options.buildDir}/${libraryName}-scss-${item}.scss";`
45 | return all
46 | }, '')
47 |
48 | async function genAdditionalData (old: string | Function | undefined, source: string, ...arg: unknown[]) {
49 | const content = isFunction(old) ? await old(source, ...arg) : (old ?? '') + source
50 | return additionalData + content
51 | }
52 |
53 | if (additionalData) {
54 | const oldVite = nuxt.options.vite.css!.preprocessorOptions!.scss.additionalData
55 | nuxt.options.vite.css.preprocessorOptions.scss.additionalData = (source: string, ...arg: unknown[]) => {
56 | return genAdditionalData(oldVite, source, ...arg)
57 | }
58 |
59 | const oldWebpack = nuxt.options.webpack.loaders.scss.additionalData
60 | nuxt.options.webpack.loaders.scss.additionalData = (source: string, ...arg: unknown[]) => {
61 | return genAdditionalData(oldWebpack, source, ...arg)
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/core/themeChalk.ts:
--------------------------------------------------------------------------------
1 | import type { NuxtTemplate } from '@nuxt/schema'
2 | import { libraryName } from '../config'
3 | import { isArray, isObject, resolveComponentPath } from '../utils'
4 | import type { ModuleOptions, ScssChalk, ScssVariables, Themes } from '../types'
5 |
6 | export function resolveThemeChalk (config: ModuleOptions) {
7 | const { themeChalk, namespace } = config
8 | const files: NuxtTemplate[] = []
9 | const common: ScssChalk = {}
10 | const themes: Themes[] = []
11 |
12 | if (!themeChalk) { return [] }
13 |
14 | Object.keys(themeChalk).forEach((key) => {
15 | if (key.startsWith('$')) {
16 | const _key = key as keyof ScssChalk
17 | common[_key] = themeChalk[_key]
18 | } else {
19 | themes.push(key as Themes)
20 | }
21 | })
22 |
23 | if (namespace && namespace !== 'el') {
24 | files.push(genNamespaceFile())
25 | }
26 |
27 | if (Object.keys(common).length) {
28 | files.push(genThemeChalkFile('common', common))
29 | }
30 |
31 | themes.forEach((type) => {
32 | const config = themeChalk[type]
33 | if (config && Object.keys(config).length) {
34 | files.push(genThemeChalkFile(type, config))
35 | }
36 | })
37 |
38 | function genNamespaceFile () : NuxtTemplate {
39 | return {
40 | filename: `${libraryName}-scss-namespace.scss`,
41 | write: true,
42 | getContents: async () => {
43 | return `@forward '${await resolveComponentPath('theme-chalk/src/mixins/config.scss', false)}' with (
44 | $namespace: '${namespace}'
45 | );`
46 | }
47 | }
48 | }
49 |
50 | function genThemeChalkFile (type: Themes | 'common', config: ScssChalk): NuxtTemplate {
51 | return {
52 | filename: `${libraryName}-scss-${type}.scss`,
53 | write: true,
54 | getContents: async () => {
55 | return `@forward '${await resolveComponentPath(`theme-chalk/src/${type}/var.scss`, false)}' with (
56 | ${genScssVariables(config)}
57 | );`
58 | }
59 | }
60 | }
61 |
62 | function genScssVariables (config: ScssChalk): string {
63 | function genValue (value: string | string[] | ScssVariables>): string {
64 | if (isArray(value)) {
65 | return `(${value.join(', ')})`
66 | } else if (isObject(value)) {
67 | return `(${Object.entries(value).reduce((all, [k, v]) => {
68 | if (!v) { return all }
69 | all.push(`'${k}': ${genValue(v)}`)
70 | return all
71 | }, [] as string[]).join(', ')})`
72 | } else {
73 | return value
74 | }
75 | }
76 |
77 | return Object.entries(config).reduce((all, [key, value]) => {
78 | if (!value) { return all }
79 | all.push(` ${key}: ${genValue(value)}`)
80 | return all
81 | }, [] as string[]).join(',\n')
82 | }
83 |
84 | return files
85 | }
86 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import type { Component } from 'vue'
2 | import { createResolver } from '@nuxt/kit'
3 | import type { NuxtConfigLayer } from '@nuxt/schema'
4 | import { allIcons, libraryName } from './config'
5 | import type { PresetComponent } from './types'
6 |
7 | export function resolvePath (path: string): Promise {
8 | const { resolvePath } = createResolver(import.meta.url)
9 | return resolvePath(path)
10 | }
11 |
12 | export async function resolveComponentPath (path: string, cache: boolean | undefined): Promise {
13 | if (cache) {
14 | return `#build/${libraryName}-cache.mjs`
15 | }
16 |
17 | return await resolvePath(`${libraryName}/${path}`)
18 | }
19 |
20 | export function getLayersDir (layers: NuxtConfigLayer[]) {
21 | const list = []
22 |
23 | for (const layer of layers) {
24 | const srcDir = layer.config.srcDir || layer.cwd
25 | if (srcDir.includes('node_modules') && layer.config.elementPlus?.importStyle !== false) {
26 | list.push(srcDir)
27 | }
28 | }
29 |
30 | return list
31 | }
32 |
33 | export function isObject (value: any): value is Record {
34 | return typeof value === 'object' && value !== null && !isArray(value)
35 | }
36 |
37 | export function isFunction (value: any): value is Function {
38 | return typeof value === 'function'
39 | }
40 |
41 | export function isArray (value: any): value is any[] {
42 | return Array.isArray(value)
43 | }
44 |
45 | export function isVueComponent (value: any): value is Component {
46 | return typeof value === 'object' && value.name && (value.props || value.emits || value.setup || value.render)
47 | }
48 |
49 | export function toArray (
50 | value: T
51 | ): T extends any[] ? T : T[] {
52 | return (isArray(value) ? value : [value]) as any
53 | }
54 |
55 | export function toRegExp (arr: string[], flags?: string): RegExp {
56 | return new RegExp(`\\b(${arr.join('|')})\\b`, flags)
57 | }
58 |
59 | export async function genLibraryImport ([name, as, from]: Required>, cache: boolean | undefined): Promise {
60 | const fromPath = await resolveComponentPath(from, cache)
61 | return `import { ${name} as ${as} } from '${fromPath}';\n`
62 | }
63 |
64 | export async function genSideEffectsImport (from: string): Promise {
65 | const fromPath = await resolvePath(from)
66 | return `import '${fromPath}';\n`
67 | }
68 |
69 | export function genIconPresets (prefix: string, from?: string): Exclude[] {
70 | return allIcons.map((name) => {
71 | return [name, `${prefix}${name}`, from] as Exclude
72 | })
73 | }
74 |
75 | export function camelize (value: string): string {
76 | return value.replace(/(^|-)(\w)/g, (a, b, c) => c.toUpperCase())
77 | }
78 |
79 | export function hyphenate (value: string): string {
80 | return value.replace(/\B([A-Z])/g, '-$1').toLowerCase()
81 | }
82 |
--------------------------------------------------------------------------------
/src/core/transformPlugin.ts:
--------------------------------------------------------------------------------
1 | import { createUnplugin } from 'unplugin'
2 | import MagicString from 'magic-string'
3 | import type { NuxtOptions } from '@nuxt/schema'
4 | import { allMethods, libraryName } from '../config'
5 | import {
6 | camelize,
7 | genLibraryImport,
8 | genSideEffectsImport,
9 | toRegExp
10 | } from '../utils'
11 | import type { PresetComponent, TransformOptions } from '../types'
12 |
13 | interface PluginOptions extends TransformOptions {
14 | layers: string[]
15 | cache: boolean | undefined
16 | sourcemap?: NuxtOptions['sourcemap']['client']
17 | transformStyles: (name: string) => undefined | string
18 | transformDirectives: (name: string) => undefined | [name: string, path: string, style?: string]
19 | }
20 |
21 | const componentsRegExp = /(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy)?([^'"]*?)["'][\s,]*[^)]*\)/g
22 | const directivesRegExp = /(?<=[ (])_?resolveDirective\(\s*["']([^'"]*?)["'][\s,]*[^)]*\)/g
23 | const methodsRegExp = toRegExp(allMethods, 'g')
24 |
25 | export const transformPlugin = createUnplugin((options: PluginOptions) => {
26 | const { cache, layers, include, exclude, sourcemap, transformStyles, transformDirectives } = options
27 |
28 | return {
29 | name: `${libraryName}:transform`,
30 | enforce: 'post',
31 | transformInclude (id) {
32 | if (layers.some(layer => id.startsWith(layer))) {
33 | return true
34 | }
35 | if (exclude.some(pattern => id.match(pattern))) {
36 | return false
37 | }
38 | if (include.some(pattern => id.match(pattern))) {
39 | return true
40 | }
41 | },
42 | async transform (code, id) {
43 | const styles = new Set()
44 | const directives: Required>[] = []
45 | const s = new MagicString(code)
46 | let no = 0
47 |
48 | const addStyles = (style?: string) => {
49 | style && styles.add(style)
50 | }
51 |
52 | s.replace(componentsRegExp, (full, lazy, name) => {
53 | addStyles(transformStyles(camelize(name)))
54 | return full
55 | })
56 |
57 | s.replace(methodsRegExp, (full, name) => {
58 | addStyles(transformStyles(camelize(name)))
59 | return full
60 | })
61 |
62 | s.replace(directivesRegExp, (full, name) => {
63 | const directiveConfig = transformDirectives(camelize(name))
64 |
65 | if (directiveConfig) {
66 | const [directive, path, style] = directiveConfig
67 | const aliasName = `__el_directive_${no}`
68 |
69 | no += 1
70 | addStyles(style)
71 | directives.push([directive, aliasName, path])
72 | return aliasName
73 | }
74 |
75 | return full
76 | })
77 |
78 | if (styles.size || directives.length) {
79 | let imports: string = ''
80 |
81 | for (const directive of directives) {
82 | imports += await genLibraryImport(directive, cache)
83 | }
84 |
85 | for (const style of styles) {
86 | imports += await genSideEffectsImport(style)
87 | }
88 |
89 | s.prepend(imports)
90 | }
91 |
92 | if (s.hasChanged()) {
93 | return {
94 | code: s.toString(),
95 | map: sourcemap
96 | ? s.generateMap({ source: id, includeContent: true })
97 | : undefined
98 | }
99 | }
100 | }
101 | }
102 | })
103 |
--------------------------------------------------------------------------------
/src/module.ts:
--------------------------------------------------------------------------------
1 | import { addPluginTemplate, addTemplate, defineNuxtModule } from '@nuxt/kit'
2 | import { defaults, libraryName } from './config'
3 | import {
4 | resolveCache,
5 | resolveComponents,
6 | resolveDirectives,
7 | resolveGlobalConfig,
8 | resolveBaseImports,
9 | resolveImports,
10 | resolveInjection,
11 | resolveOptions,
12 | resolveStyles,
13 | resolveTeleports,
14 | resolveThemeChalk,
15 | resolveThemes,
16 | resolveMethods,
17 | transformPlugin,
18 | localePlugin
19 | } from './core/index'
20 | import { getLayersDir } from './utils'
21 | import type { ModuleOptions } from './types'
22 | export type { ModuleOptions } from './types'
23 |
24 | export default defineNuxtModule({
25 | meta: {
26 | name: libraryName,
27 | configKey: 'elementPlus',
28 | compatibility: {
29 | nuxt: '>=3'
30 | }
31 | },
32 | defaults,
33 | setup (options, nuxt) {
34 | const layers = getLayersDir(nuxt.options._layers)
35 |
36 | // disable the `cache` option when building applications
37 | if (!nuxt.options.dev) {
38 | options.cache = false
39 | }
40 |
41 | resolveOptions(options)
42 | resolveThemes(options)
43 | resolveBaseImports(options)
44 | nuxt.options.imports.autoImport !== false && resolveImports(options)
45 | nuxt.options.components !== false && resolveComponents(options)
46 | options.cache && addTemplate(resolveCache(options))
47 | options.globalConfig && addPluginTemplate(resolveGlobalConfig(options))
48 | options.importStyle === 'scss' && options.themeChalk && resolveThemeChalk(options).map(addTemplate)
49 |
50 | if (nuxt.options.ssr !== false) {
51 | addPluginTemplate(resolveInjection(options))
52 | addPluginTemplate(resolveTeleports(options))
53 | options.installMethods.length && addPluginTemplate(resolveMethods(options))
54 | }
55 |
56 | nuxt.hook('vite:extendConfig', (config, { isClient }) => {
57 | const mode = isClient ? 'client' : 'server'
58 |
59 | config.plugins = config.plugins || []
60 | config.plugins.push(transformPlugin.vite({
61 | layers,
62 | cache: options.cache,
63 | include: options.include,
64 | exclude: options.exclude,
65 | sourcemap: nuxt.options.sourcemap[mode],
66 | transformStyles: name => resolveStyles(options, name),
67 | transformDirectives: name => resolveDirectives(options, name)
68 | }))
69 |
70 | if (options.defaultLocale !== 'en') {
71 | config.plugins.push(localePlugin.vite({
72 | sourcemap: nuxt.options.sourcemap[mode],
73 | locale: options.defaultLocale
74 | }))
75 | }
76 | })
77 |
78 | nuxt.hook('webpack:config', (configs) => {
79 | configs.forEach((config) => {
80 | const mode = config.name === 'client' ? 'client' : 'server'
81 |
82 | config.plugins = config.plugins || []
83 | config.plugins.push(transformPlugin.webpack({
84 | layers,
85 | cache: options.cache,
86 | include: options.include,
87 | exclude: options.exclude,
88 | sourcemap: nuxt.options.sourcemap[mode],
89 | transformStyles: name => resolveStyles(options, name),
90 | transformDirectives: name => resolveDirectives(options, name)
91 | }))
92 |
93 | if (options.defaultLocale !== 'en') {
94 | config.plugins.push(localePlugin.webpack({
95 | sourcemap: nuxt.options.sourcemap[mode],
96 | locale: options.defaultLocale
97 | }))
98 | }
99 | })
100 | })
101 | }
102 | })
103 |
--------------------------------------------------------------------------------
/playground/components/other-card.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {{ item }}
48 |
49 |
50 |
51 |
52 |
53 | -
54 | {{ i }}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 1
63 |
64 |
65 |
66 |
67 | 2
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
138 |
--------------------------------------------------------------------------------
/playground/components/navigation-card.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 | Offset top 120px
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Title
19 |
20 |
21 |
22 | homepage
23 |
24 |
25 | promotion management
26 |
27 | promotion list
28 | promotion detail
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | User
41 |
42 |
43 | Config
44 |
45 |
46 | Role
47 |
48 |
49 | Task
50 |
51 |
52 |
53 |
54 |
55 |
56 |
67 | Fixed Top Block
68 |
69 |
70 |
78 | part1
79 |
80 |
88 | part2
89 |
90 |
98 | part3
99 |
100 |
101 |
102 |
103 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
127 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import * as AllComponents from 'element-plus'
2 | import * as AllIcons from '@element-plus/icons-vue'
3 | import type { ElIdInjectionContext, ElZIndexInjectionContext } from 'element-plus'
4 | import type { Component } from 'vue'
5 | import { isVueComponent } from './utils'
6 | import type { ModuleOptions, PresetDirectives, PresetImport } from './types'
7 |
8 | export const libraryName = 'element-plus'
9 |
10 | export const iconLibraryName = '@element-plus/icons-vue'
11 |
12 | export const optimizeDeps = ['dayjs', 'dayjs/plugin/*.js', 'lodash-unified']
13 |
14 | const allComponents = Object.entries(AllComponents).reduce((all, [key, item]) => {
15 | const regExp = /^El[A-Z]\w+/
16 | if (isVueComponent(item) && regExp.test(key) && regExp.test((item as Component).name ?? '')) {
17 | all.push(key)
18 | }
19 | return all
20 | }, [])
21 |
22 | export const allIcons = Object.keys(AllIcons)
23 |
24 | export const allMethods: string[] = [
25 | 'ElLoading',
26 | 'ElMessage',
27 | 'ElMessageBox',
28 | 'ElNotification'
29 | ]
30 |
31 | const allImports: PresetImport[] = []
32 |
33 | const allBaseImports: PresetImport[] = [
34 | ['ID_INJECTION_KEY', 'es/hooks/use-id/index.mjs'],
35 | ['ZINDEX_INJECTION_KEY', 'es/hooks/use-z-index/index.mjs'],
36 | ['provideGlobalConfig', 'es/components/config-provider/src/hooks/use-global-config.mjs']
37 | ]
38 |
39 | const allNoStylesComponents: string[] = [
40 | 'ElAutoResizer',
41 | 'ElCollection',
42 | 'ElCollectionItem',
43 | 'ElTooltipV2'
44 | ]
45 |
46 | const allDirectives: PresetDirectives = {
47 | Loading: ['ElLoadingDirective', 'ElLoading'],
48 | Popover: ['ElPopoverDirective', 'ElPopover'],
49 | InfiniteScroll: 'ElInfiniteScroll'
50 | }
51 |
52 | const allSubComponents: Record = {
53 | ElAnchor: ['ElAnchorLink'],
54 | ElBreadcrumb: ['ElBreadcrumbItem'],
55 | ElButton: ['ElButtonGroup'],
56 | ElCarousel: ['ElCarouselItem'],
57 | ElCheckbox: ['ElCheckboxButton', 'ElCheckboxGroup'],
58 | ElCollapse: ['ElCollapseItem'],
59 | ElCollection: ['ElCollectionItem'],
60 | ElContainer: ['ElAside', 'ElFooter', 'ElHeader', 'ElMain'],
61 | ElDescriptions: ['ElDescriptionsItem'],
62 | ElDropdown: ['ElDropdownItem', 'ElDropdownMenu'],
63 | ElForm: ['ElFormItem'],
64 | ElMenu: ['ElMenuItem', 'ElMenuItemGroup', 'ElSubMenu'],
65 | ElPopper: ['ElPopperArrow', 'ElPopperContent', 'ElPopperTrigger'],
66 | ElRadio: ['ElRadioGroup', 'ElRadioButton'],
67 | ElSkeleton: ['ElSkeletonItem'],
68 | ElSelect: ['ElOption', 'ElOptionGroup'],
69 | ElSplitter: ['ElSplitterPanel'],
70 | ElSteps: ['ElStep'],
71 | ElTable: ['ElTableColumn'],
72 | ElTableV2: ['ElAutoResizer'],
73 | ElTabs: ['ElTabPane'],
74 | ElTimeline: ['ElTimelineItem'],
75 | ElTour: ['ElTourStep']
76 | }
77 |
78 | const defaultInjectionID: ElIdInjectionContext = {
79 | prefix: 1024,
80 | current: 0
81 | }
82 |
83 | const defaultInjectionZIndex: ElZIndexInjectionContext = {
84 | current: 0
85 | }
86 |
87 | const defaultInclude: RegExp[] = [
88 | /\.vue$/,
89 | /\.vue\?vue/,
90 | /\.vue\?v=/,
91 | /\.((c|m)?j|t)sx?$/
92 | ]
93 |
94 | const defaultExclude: RegExp[] = [
95 | /[\\/]node_modules[\\/]/,
96 | /[\\/]\.git[\\/]/,
97 | /[\\/]\.nuxt[\\/]/
98 | ]
99 |
100 | export const defaults: ModuleOptions = {
101 | components: allComponents,
102 | subComponents: allSubComponents,
103 | directives: allDirectives,
104 | imports: allImports,
105 | baseImports: allBaseImports,
106 | importStyle: 'css',
107 | themes: [],
108 | noStylesComponents: allNoStylesComponents,
109 | injectionID: defaultInjectionID,
110 | injectionZIndex: defaultInjectionZIndex,
111 | include: defaultInclude,
112 | exclude: defaultExclude,
113 | namespace: 'el',
114 | defaultLocale: 'en',
115 | appendTo: [],
116 | installMethods: [],
117 | icon: 'ElIcon'
118 | }
119 |
--------------------------------------------------------------------------------
/playground/app.vue:
--------------------------------------------------------------------------------
1 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Dropdown List
57 |
58 |
59 |
60 |
61 |
62 |
63 | Action 1
64 | Action 2
65 | Action 3
66 | Action 4
67 | Action 5
68 |
69 |
70 |
71 |
77 |
78 |
79 | Popover
80 |
81 |
82 | test1
83 |
84 |
85 | {{ t('el.colorpicker.confirm') }}
86 |
87 |
88 | Open Dialog
89 |
90 |
91 | Open Drawer
92 |
93 |
94 | Change Theme
95 |
96 |
97 | Lazy Button
98 |
99 |
100 | el-text
101 |
102 |
103 | element-plus
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | Hi there!
121 |
122 |
127 | This is a message
128 |
129 |
135 |
136 |
137 |
138 |
139 |
140 |
148 |
--------------------------------------------------------------------------------
/playground/components/data-card.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
52 |
53 |
54 | Tag 2
55 |
56 |
57 |
58 | Checked
59 |
60 |
61 |
62 | Begin Tour
63 |
64 |
65 |
66 |
67 |
72 | Put you files here.
73 |
74 |
79 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | Consistent with real life: in line with the process and logic of real
97 | life, and comply with languages and habits that the users are used to;
98 |
99 |
100 | Consistent within interface: all elements should be consistent, such
101 | as: design style, icons and texts, position of elements, etc.
102 |
103 |
104 |
105 |
106 | Operation feedback: enable the users to clearly perceive their
107 | operations by style updates and interactive effects;
108 |
109 |
110 | Visual feedback: reflect current state by updating or rearranging
111 | elements of the page.
112 |
113 |
114 |
115 |
116 |
117 |
122 | {{ activity.content }}
123 |
124 |
125 |
126 |
127 |
128 | kooriookami
129 |
130 |
131 | 18100000000
132 |
133 |
134 | Suzhou
135 |
136 |
137 |
138 | School
139 |
140 |
141 |
142 | No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province
143 |
144 |
145 |
146 |
147 |
148 |
153 |
154 |
155 | Back
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | {{ item }}
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
189 |
--------------------------------------------------------------------------------
/playground/components/form-card.vue:
--------------------------------------------------------------------------------
1 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
144 |
145 |
146 |
147 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 | Click to upload
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | # Element Plus Nuxt
14 |
15 | > [Element Plus](https://element-plus.org) module for [Nuxt](https://nuxt.com)
16 |
17 | ## Features
18 |
19 | - Automatically import components and styles on demand.
20 | - Automatically import directives and styles on demand.
21 | - Automatically import icons from [@element-plus/icons-vue](https://github.com/element-plus/element-plus-icons).
22 | - Automatically import of ElMessage, ElNotification and other methods.
23 | - Automatically inject the ID_INJECTION_KEY and ZINDEX_INJECTION_KEY into Vue.
24 | - Automatically inject the teleport markup into the correct location in the final page HTML.
25 |
26 | ## Installation
27 |
28 | > [!WARNING]
29 | > Since the [dayjs](https://github.com/iamkun/dayjs) used internally by element-plus is not a [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), in order to ensure that it can be converted into a JavaScript modules before startup, you need to add a `.npmrc` file to the root directory of the project and add the following configuration:
30 | > ```
31 | > shamefully-hoist=true
32 | > node-linker=hoisted
33 | > ```
34 | > Or install the `dayjs` dependency separately.
35 |
36 | ```bash
37 | npx nuxi@latest module add element-plus
38 | # or
39 | npm i element-plus @element-plus/nuxt -D
40 | ```
41 |
42 | ## Configuration
43 |
44 | > [!WARNING]
45 | > At present, the method cannot automatically obtain the context, and you need to manually configure [installMethods](#installmethods) in the options.
46 |
47 | ```ts
48 | export default defineNuxtConfig({
49 | modules: [
50 | '@element-plus/nuxt'
51 | ],
52 | elementPlus: { /** Options */ }
53 | })
54 | ```
55 |
56 | ## Usage
57 |
58 | ```vue
59 |
60 | button
61 | button
62 | lazy button
63 |
64 | ```
65 |
66 | Reference [Nuxt documentation](https://nuxt.com/docs/guide/directory-structure/components) and [playground](./playground/app.vue) use.
67 |
68 | ## Options
69 |
70 | ### importStyle
71 |
72 | - Type: `'css' | 'scss' | boolean`
73 | - Default: `css`
74 |
75 | import style css or sass(scss) with components, disable automatically import styles with `false`.
76 |
77 | ### themes
78 |
79 | - Type: `array`
80 |
81 | A list of themes that require import styles automatically.
82 |
83 | e.g. `['dark']`
84 |
85 | ### icon
86 |
87 | - Type: `string | false`
88 | - Default: `ElIcon`
89 |
90 | Icon prefix name, disable automatically import icon with `false`.
91 |
92 | ### installMethods
93 |
94 | - Type: `array`
95 |
96 | List of methods that need to be installed.
97 |
98 | e.g. `['ElLoading', 'ElMessage', 'ElMessageBox', 'ElNotification']`
99 |
100 | ### namespace
101 |
102 | - Type: `string`
103 | - Default: `el`
104 |
105 | When you change the global namespace, you must change it here as well.
106 |
107 | ### defaultLocale
108 |
109 | - Type: `string`
110 |
111 | Replace default locale, you can find locale list [here](https://github.com/element-plus/element-plus/tree/dev/packages/locale/lang)
112 |
113 | e.g. `'zh-cn'`
114 |
115 | ### cache
116 |
117 | - Type: `boolean`
118 | - Default: `false`
119 |
120 | Whether to cache the element-plus components and directives. **Only effective in development mode**.
121 |
122 | If you enable this feature, you will get faster loading speed in development mode.
123 |
124 | ### themeChalk
125 |
126 | - Type: `object`
127 |
128 | Configure SCSS variables for generating custom themes. **Only effective when `importStyle` is `scss`**.
129 |
130 | e.g.
131 |
132 | ```ts
133 | {
134 | $colors: {
135 | primary: { base: 'rgba(107,33,168, 1)' }
136 | },
137 | dark: {
138 | $colors: {
139 | primary: { base: 'rgb(242, 216, 22)' }
140 | }
141 | }
142 | }
143 | ```
144 |
145 | ### globalConfig
146 |
147 | - Type: `object`
148 |
149 | Set global configuration, such as modifying the default `size` and `z-index` of the component.
150 |
151 | e.g. `{ size: 'small', zIndex: 3000 }`
152 |
153 | ### injectionID
154 |
155 | - Type: `object`
156 | - Default: `{ prefix: 1024, current: 0 }`
157 |
158 | Automatically inject the ID_INJECTION_KEY into Vue.
159 |
160 | ### injectionZIndex
161 |
162 | - Type: `object`
163 | - Default: `{ current: 0 }`
164 |
165 | Automatically inject the Z_INDEX_INJECTION_KEY into Vue.
166 |
167 | ### appendTo
168 |
169 | - Type: `array`
170 |
171 | When you modify the `append-to` props in all based on ElTooltip components, you need to add the value here.
172 |
173 | ### components
174 |
175 | - Type: `array`
176 |
177 | If there are components that are not imported automatically from Element Plus, you need to add the component name here.
178 |
179 | e.g. `['ElSubMenu']`
180 |
181 | ### subComponents
182 |
183 | - Type: `object`
184 |
185 | A map of components that the definition file of subComponent is in its parent component.
186 |
187 | ### directives
188 |
189 | - Type: `object`
190 |
191 | If there are directives that are not imported automatically from Element Plus, you need to add the directive name here.
192 |
193 | ### imports
194 |
195 | - Type: `array`
196 |
197 | If you wish to add automatically import content from Element Plus, you can add it here.
198 |
199 | e.g.
200 |
201 | ```ts
202 | [
203 | ['useLocale', 'es/hooks/use-locale/index.mjs'],
204 | [['castArray', 'unique'], 'es/utils/arrays.mjs']
205 | ],
206 | ```
207 |
208 | ### baseImports
209 |
210 | - Type: `array`
211 |
212 | List of imports that will be imported whether if autoImports is disabled.
213 |
214 | ### noStylesComponents
215 |
216 | - Type: `array`
217 |
218 | When a component incorrectly loads styles, you need to add the component name here.
219 |
220 | ### include
221 |
222 | - Type: `array`
223 | - Default: `[ /\.vue$/, /\.vue\?vue/, /\.vue\?v=/, /\.((c|m)?j|t)sx?$/]`
224 |
225 | Include files that need to automatically import styles.
226 |
227 | ### exclude
228 |
229 | - Type: `array`
230 | - Default: `[/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/]`
231 |
232 | Exclude files that do not require the automatic import of styles.
233 |
234 | ## Template
235 |
236 | [element-plus-nuxt-starter](https://github.com/element-plus/element-plus-nuxt-starter)
237 |
238 | ## Development
239 |
240 | - Run `pnpm i` to install the dependencies.
241 | - Run `pnpm dev:prepare` to generate type stubs.
242 | - Run `pnpm dev` to start playground in development mode.
243 | - Run `pnpm dev:build` to build playground.
244 | - Run `pnpm dev:start` to locally preview playground.
245 | - Run `pnpm build` to build this project.
246 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { ElIdInjectionContext, ElZIndexInjectionContext, ConfigProviderContext } from 'element-plus'
2 |
3 | export type Themes = 'dark'
4 |
5 | /** name: export name from the library, as: the name you want to use in your project, from: the name of library */
6 | export type PresetComponent = string | [name: string, as?: string, from?: string]
7 |
8 | /** name: export name from the library, path: the file path in the component directory */
9 | export type PresetImport = [name: string | string[], path: string]
10 |
11 | /** directive: export name from the library, name: export name with style */
12 | export type PresetDirectives = Record
13 |
14 | /** Used to filter files that need to automatically import styles and other functions */
15 | export interface TransformOptions {
16 | include: RegExp[]
17 | exclude: RegExp[]
18 | }
19 |
20 | export type ScssVariables = { [k in Key]?: Value } & { [k: string]: Value }
21 |
22 | export interface ScssChalk {
23 | '$colors'?: Partial> & ScssVariables<'white' | 'black', string | { base?: string }>
24 | '$text-color'?: ScssVariables<'primary' | 'regular' | 'secondary' | 'placeholder' | 'disabled'>
25 | '$bg-color'?: ScssVariables<'' | 'page' | 'overlay'>
26 | '$border-color'?: ScssVariables<'' | 'light' | 'lighter' | 'extra-light' | 'dark' | 'darker'>
27 | '$fill-color'?: ScssVariables<'' | 'light' | 'lighter' | 'extra-light' | 'dark' | 'darker' | 'blank'>
28 | '$border-radius'?: ScssVariables<'base' | 'small' | 'round' | 'circle'>
29 | '$box-shadow'?: ScssVariables<'' | 'light' | 'lighter' | 'dark', string | string[]>
30 | '$font-family'?: ScssVariables<''>
31 | '$font-size'?: ScssVariables<'extra-large' | 'large' | 'medium' | 'base' | 'small' | 'extra-small'>
32 | '$z-index'?: ScssVariables<'normal' | 'top' | 'popper'>
33 | '$common-component-size'?: ScssVariables<'large' | 'default' | 'small'>
34 | '$overlay-color'?: ScssVariables<'' | 'light' | 'lighter'>
35 | '$mask-color'?: ScssVariables<'' | 'extra-light'>
36 | [key: `$${string}`]: string | undefined | ScssVariables>
37 | }
38 |
39 | export type ThemeChalk = ScssChalk & Partial>
40 |
41 | export interface ModuleOptions extends TransformOptions {
42 | /**
43 | * A list of components that need to be automatically imported externally.
44 | *
45 | * @default 'from element-plus/es/component'
46 | *
47 | * If there are components that are not imported automatically from Element Plus, you need to add the component name here.
48 | *
49 | * @example
50 | * ```ts
51 | * ['ElSubMenu']
52 | * ```
53 | */
54 | components: PresetComponent[]
55 | /**
56 | * A map of components that the definition file of subComponent is in its parent component.
57 | */
58 | subComponents: Record
59 | /**
60 | * A list of directives that need to be automatically imported externally.
61 | *
62 | * @default
63 | * ```ts
64 | * {
65 | * Loading: ['ElLoadingDirective', 'ElLoading'],
66 | * Popover: ['ElPopoverDirective', 'ElPopover'],
67 | * InfiniteScroll: 'ElInfiniteScroll'
68 | * }
69 | * ```
70 | */
71 | directives: PresetDirectives
72 | /**
73 | * A list of imports that need to be automatically imported externally.
74 | *
75 | * When you need to add automatically import content from Element Plus, you can add it here.
76 | *
77 | * @example
78 | * ```ts
79 | * [
80 | * ['useLocale', 'es/hooks/use-locale/index.mjs'],
81 | * [['castArray', 'unique'], 'es/utils/arrays.mjs']
82 | * ]
83 | * ```
84 | *
85 | * @before
86 | * ```ts
87 | * import { useLocale } from 'element-plus'
88 | * const { t } = useLocale()
89 | * ```
90 | *
91 | * @after
92 | * ```ts
93 | * const { t } = useLocale()
94 | * ```
95 | */
96 | imports: PresetImport[]
97 | /**
98 | *
99 | * List of imports that will be imported whether if autoImports is disabled.
100 | *
101 | * @default
102 | * ```ts
103 | * [
104 | * ["ID_INJECTION_KEY", "es/hooks/use-id/index.mjs"],
105 | * ["ZINDEX_INJECTION_KEY", "es/hooks/use-z-index/index.mjs"],
106 | * ["provideGlobalConfig", "es/components/config-provider/src/hooks/use-global-config.mjs"],
107 | * ]
108 | * ```
109 | */
110 | baseImports: PresetImport[]
111 | /**
112 | * import style css or scss with components
113 | *
114 | * @default 'css'
115 | *
116 | * Disable automatically import styles with `false`
117 | */
118 | importStyle: 'css' | 'scss' | boolean
119 | /**
120 | * A list of themes that require importing styles automatically.
121 | *
122 | * Currently, only [dark](https://element-plus.org/en-US/guide/dark-mode.html) is supported.
123 | *
124 | * @example
125 | * ```ts
126 | * ['dark']
127 | * ```
128 | */
129 | themes: Themes[]
130 | /**
131 | * A list of component names that have no styles, so resolving their styles file should be prevented
132 | *
133 | * @default
134 | * ```ts
135 | * ['ElAutoResizer', 'ElTooltipV2']
136 | * ```
137 | */
138 | noStylesComponents: string[]
139 | /**
140 | * We need to inject the same ID value into the server side and client side to ensure that the code generated on the server is the same as that on the client, so as to avoid hydrate errors.
141 | *
142 | * @default
143 | * ```ts
144 | * { prefix: 1024, current: 0 }
145 | * ```
146 | */
147 | injectionID: ElIdInjectionContext
148 | /**
149 | * We need to inject an initial z-index value to ensure that the server side and client side generate the same z-index value, so as to avoid hydration errors.
150 | *
151 | * @default
152 | * ```ts
153 | * { current: 0 }
154 | * ```
155 | */
156 | injectionZIndex: ElZIndexInjectionContext
157 | /**
158 | * Global component className prefix.
159 | *
160 | * When you change the global namespace, you must change it here as well.
161 | *
162 | * @default 'el'
163 | */
164 | namespace: string
165 | /**
166 | * Which element the tooltip CONTENT appends to.
167 | *
168 | * When you modify the `append-to` props in all based on ElTooltip components, you need to add the value here, to avoid hydrate errors.
169 | *
170 | * If you used `Teleport` to teleport a part of a component's template into a DOM node near the `` tag, you can also add this ID here. The internal plug-in will automatically handle hydrate errors.
171 | */
172 | appendTo: string[]
173 | /**
174 | * Icon prefix name.
175 | *
176 | * To avoid the duplication of icon names with native DOM or other components, we recommend adding a prefix to the icon.
177 | *
178 | * @default 'ElIcon'
179 | *
180 | * Disable automatically import icon with `false`
181 | */
182 | icon: false | string
183 | /**
184 | * Replace default locale, you can find locale list [here](https://github.com/element-plus/element-plus/tree/dev/packages/locale/lang).
185 | *
186 | * @default 'en'
187 | * @example 'zh-cn'
188 | */
189 | defaultLocale: string
190 | /**
191 | * Set global configuration, such as modifying the default size and z-index of the component.
192 | *
193 | * @example
194 | * ```ts
195 | * { size: 'small', zIndex: 3000 }
196 | * ```
197 | */
198 | globalConfig?: ConfigProviderContext
199 | /**
200 | * List of methods that need to be installed.
201 | *
202 | * @example
203 | * ```ts
204 | * ['ElLoading', 'ElMessage', 'ElMessageBox', 'ElNotification']
205 | * ```
206 | */
207 | installMethods: Array<'ElLoading' | 'ElMessage' | 'ElMessageBox' | 'ElNotification'>
208 | /**
209 | * Whether to cache the element-plus components and directives. **Only effective in development mode**.
210 | *
211 | * If you enable this feature, you will get faster loading speed in development mode.
212 | *
213 | * @default 'false'
214 | */
215 | cache?: boolean
216 | /**
217 | * Configure SCSS variables for generating custom themes. **Only effective when `importStyle` is `scss`**.
218 | */
219 | themeChalk?: ThemeChalk
220 | }
221 |
--------------------------------------------------------------------------------