├── readme.jpg ├── src ├── js │ ├── svelte-code-helper.js │ └── mix.js └── SvelteDirectServiceProvider.php ├── CHANGELOG.md ├── LICENSE.md ├── .php_cs.dist.php ├── composer.json └── README.md /readme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickpoulos/laravel-svelte-direct/HEAD/readme.jpg -------------------------------------------------------------------------------- /src/js/svelte-code-helper.js: -------------------------------------------------------------------------------- 1 | const html2 = require("htmlparser2"); 2 | 3 | exports.findSvelteProps = (svelteCode) => /(?<=export ).+?((?= =)|(?==)|(?=;))/g[Symbol.match](svelteCode)?.map((match) => match.replace(/let |const |var /g, '')) ?? []; 4 | exports.findSvelteTagName = (svelteCode) => { 5 | let tag = null; 6 | const parser = new html2.Parser({ 7 | onopentag(name, attributes) { 8 | if (name.toLowerCase() === 'svelte:options') { 9 | tag = attributes?.tag.trim() || null 10 | } 11 | } 12 | }); 13 | 14 | parser.write(svelteCode); 15 | parser.end(); 16 | return tag; 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-svelte-direct` will be documented in this file. 4 | 5 | ## 0.1.0 - 2021-06-11 6 | 7 | - Refactor the generated bootstrap JavaScript to use the `svelte-tag` package under the hood 8 | 9 | - Replace custom @sveltedirect Blade directive with Laravel's built-in @stack('sveltedirect') 10 | 11 | - Migrate Laravel Svelte Direct Mix from its own package into this one 12 | 13 | - Improve Regex for tag matching 14 | 15 | - Refactor the main ServiceProvider to have cleaner functions 16 | 17 | - Added several tests for the rest of the ServiceProvider functions 18 | 19 | ## 0.0.1b - 2021-05-24 20 | 21 | - Hotfix to add default values for class vars, was causing errors in some cases 22 | 23 | ## 0.0.1 - 2021-05-22 24 | 25 | - Initial proof-of-concept packages created 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Nick Poulos 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'method_argument_space' => [ 30 | 'on_multiline' => 'ensure_fully_multiline', 31 | 'keep_multiple_spaces_after_comma' => true, 32 | ], 33 | 'single_trait_insert_per_statement' => true, 34 | ]) 35 | ->setFinder($finder); 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nickpoulos/laravel-svelte-direct", 3 | "description": "Use Svelte components seamlessly in Laravel Blade Templates", 4 | "keywords": [ 5 | "nickpoulos", 6 | "laravel", 7 | "laravel-svelte-direct" 8 | ], 9 | "homepage": "https://github.com/nickpoulos/laravel-svelte-direct", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Nick Poulos", 14 | "email": "nick@nickpoulos.info", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0", 20 | "spatie/laravel-package-tools": "^1.4.3", 21 | "illuminate/contracts": "^8.37" 22 | }, 23 | "require-dev": { 24 | "brianium/paratest": "^6.2", 25 | "nunomaduro/collision": "^5.3", 26 | "orchestra/testbench": "^6.15", 27 | "phpunit/phpunit": "^9.3", 28 | "spatie/laravel-ray": "^1.9", 29 | "vimeo/psalm": "^4.4" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Nickpoulos\\SvelteDirect\\": "src" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Nickpoulos\\SvelteDirect\\Tests\\": "tests" 39 | } 40 | }, 41 | "scripts": { 42 | "psalm": "vendor/bin/psalm", 43 | "test": "./vendor/bin/testbench package:test --parallel --no-coverage", 44 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 45 | }, 46 | "config": { 47 | "sort-packages": true 48 | }, 49 | "extra": { 50 | "laravel": { 51 | "providers": [ 52 | "Nickpoulos\\SvelteDirect\\SvelteDirectServiceProvider" 53 | ], 54 | "aliases": { 55 | "SvelteDirect": "Nickpoulos\\SvelteDirect\\SvelteDirectFacade" 56 | } 57 | } 58 | }, 59 | "minimum-stability": "dev", 60 | "prefer-stable": true 61 | } 62 | -------------------------------------------------------------------------------- /src/SvelteDirectServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadManifestFile(); 32 | $this->app['blade.compiler']->precompiler([$this, 'precompiler']); 33 | } 34 | 35 | /** 36 | * Provide the default path to the SvelteDirect manifest file 37 | * 38 | * @todo allow control via proper config file 39 | * 40 | * @internal 41 | * @return string 42 | */ 43 | public function defaultManifestPath() : string 44 | { 45 | return App::bootstrapPath('cache/svelte-direct-components.php'); 46 | } 47 | 48 | /** 49 | * Loads the "tag name to JavaScript file" mapping 50 | * aka manifest file 51 | * 52 | * @internal 53 | * @param string|null $manifestFilePath 54 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 55 | */ 56 | public function loadManifestFile(?string $manifestFilePath = null) : void 57 | { 58 | $files = new Filesystem(); 59 | $manifestPath = $manifestFilePath ?? $this->defaultManifestPath(); 60 | $this->manifest = file_exists($manifestPath) ? $files->getRequire($manifestPath):[]; 61 | } 62 | 63 | /** 64 | * Our precompiler function that finds any Svelte component tags 65 | * and then appends the proper call to the @stack Blade directive 66 | * to our existing Blade template code 67 | * 68 | * @param string $viewTemplateCode 69 | * @return string 70 | */ 71 | public function precompiler(string $viewTemplateCode) : string 72 | { 73 | collect($this->manifest)->each(function (string $jsFile, string $tag) use (&$viewTemplateCode) { 74 | $check = $this->findPositionOfSvelteTagInBlade($viewTemplateCode, $tag); 75 | if (! $check || in_array($tag, $this->loadedTags, true)) { 76 | return; // skip 77 | } 78 | $pushDirective = $this->generatePushDirective([$tag]); 79 | $viewTemplateCode = substr_replace($viewTemplateCode, $pushDirective, $check - 1, 0); 80 | $this->loadedTags = array_merge($this->loadedTags, [$tag]); 81 | }); 82 | 83 | return $viewTemplateCode; 84 | } 85 | 86 | 87 | /** 88 | * Given some Blade template code, and one of our Svelte component tags 89 | * Check if the tag exists in the code, if so, return the position of the first occurrence 90 | * 91 | * @param string $viewTemplateCode 92 | * @param string $tag 93 | * @return Collection 94 | * @internal 95 | */ 96 | public function findPositionOfSvelteTagInBlade(string $viewTemplateCode, string $tag) : ?int 97 | { 98 | $pattern = "/(?<=<)\s*(?:{$tag})(?=\s|>|\/)+/"; 99 | preg_match_all($pattern, $viewTemplateCode, $matches, PREG_OFFSET_CAPTURE); 100 | 101 | return collect($matches[0])->pluck(1)->first(); 102 | } 103 | 104 | /** 105 | * Create the @push directive code for the given Svelte tags 106 | * 107 | * @internal 108 | * @param array $tagsToLoad 109 | * @return string 110 | */ 111 | public function generatePushDirective(array $tagsToLoad) : string 112 | { 113 | return "@push('sveltedirect')" . PHP_EOL . 114 | $this->generateScriptHtml($tagsToLoad) 115 | . PHP_EOL . "@endpush"; 116 | } 117 | 118 | 119 | /** 120 | * Generate the script tag HTML to load our component JavaScript file(s) 121 | * for a given set of tags 122 | * 123 | * @internal 124 | * @param array $tagsToLoad 125 | * @return string 126 | */ 127 | public function generateScriptHtml(array $tagsToLoad) : string 128 | { 129 | return array_reduce($tagsToLoad, function ($previous, $current) { 130 | return $previous . '' . PHP_EOL; 131 | }, ''); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/nickpoulos/laravel-svelte-direct.svg?style=flat-square)](https://packagist.org/packages/nickpoulos/laravel-svelte-direct) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/nickpoulos/laravel-svelte-direct/run-tests?label=tests)](https://github.com/nickpoulos/laravel-svelte-direct/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/nickpoulos/laravel-svelte-direct/Check%20&%20fix%20styling?label=code%20style)](https://github.com/nickpoulos/laravel-svelte-direct/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/nickpoulos/laravel-svelte-direct.svg?style=flat-square)](https://packagist.org/packages/nickpoulos/laravel-svelte-direct) 7 | 8 | ## What? 9 | Use Svelte Components from within your Laravel Blade Templates -- totally seamlessly. 10 | 11 | ## Why? 12 | 13 | Modern JavaScript has been a gift and a curse. An amazing gift for developing front ends, plus a whole lot of cursing trying to get it all configured and setup. 14 | 15 | Things that used to be very simple became increasingly complex overnight. With build steps, webpack, SSR, code-splitting, and everything else, it can get overwhelming quick. 16 | 17 | There have been several awesome attempts to get the best of both worlds, especially within the Laravel community. Projects like Livewire and Alpine.js are amazing and really inspired the creation of this project. 18 | 19 | Lately I have really taken a liking to Svelte, a different take on the typical React/Vue style application. It was refreshing to get declarative, reactive, single file components, without all the boilerplate. But coming from PHP & Laravel, I still want my Blade templates and server side rendering (ya know the old school way). 20 | 21 | Normally in this situation, Laravel is just there to serve the shell of the DOM, and then have Svelte/Vue/React take over your entire body tag, or very large chunks of your DOM. 22 | 23 | But I like eating my cake too, and so this little project was born. 24 | 25 | 26 | ## How? 27 | 28 | This package has two main pieces. 29 | 30 | - A Laravel Mix plugin that compiles each of your Svelte components into their own bite-sized JS files 31 | 32 | - A Blade Pre-Compiler that scans Blade templates identifies your Svelte component tags, and loads the right component JS automatically 33 | 34 | ### Install Laravel Svelte Direct 35 | 36 | ```bash 37 | composer require nickpoulos/laravel-svelte-direct 38 | ``` 39 | 40 | ### Configure Laravel Mix 41 | webpack.mix.js 42 | ```javascript 43 | const mix = require('laravel-mix'); 44 | require('./vendor/nickpoulos/laravel-svelte-direct/js/mix'); 45 | 46 | mix.svelteDirect('resources/js/Components', 'public/js'); 47 | 48 | ``` 49 | 50 | ### Write Your Svelte Components 51 | 52 | Write your Svelte components as your normally would, except for two small additions that we will add to the top of our file. Both are part of the official Svelte docs/spec and are not custom syntax. 53 | ```html 54 | 55 | 56 | ``` 57 | The options tag tells Svelte (and Svelte Direct), what the component's HTML tag should be. Normally this technique is only used in Svelte when compiling to WebComponents (more on that later). But it is the perfect mechanism for our cause as well. 58 | 59 | The comment tag tells Svelte to ignore when we don't have `customElement` set to true. 60 | 61 | 62 | ### Configure Blade Template 63 | 64 | In your applications's main Blade layout/component, add the `@stack('sveltedirect')'`above your ending `` tag. 65 | 66 | Feel free to add your Svelte component anywhere inside the Blade HTML. You will notice the tag we use in the HTML below matches the `` tag attribute above. 67 | 68 | example.blade.php 69 | ```php 70 | 71 | 72 | 73 | My Example App 74 | 75 | 76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 | 86 | 89 | 90 | 91 | @stack('sveltedirect') 92 | 93 | 94 | 95 | 96 | 97 | ``` 98 | 99 | ## Testing 100 | 101 | ```bash 102 | composer test 103 | ``` 104 | 105 | ## Changelog 106 | 107 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 108 | 109 | ## Contributing 110 | 111 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 112 | 113 | ## Security Vulnerabilities 114 | 115 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 116 | 117 | ## Credits 118 | 119 | - [Nick Poulos](https://github.com/nickpoulos) 120 | - [All Contributors](../../contributors) 121 | 122 | ## License 123 | 124 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 125 | -------------------------------------------------------------------------------- /src/js/mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | const { resolve, basename } = require('path'); 3 | const { readdir, writeFile, readFile, unlink } = require('fs').promises; 4 | const { findSvelteProps, findSvelteTagName } = require('./svelte-code-helper'); 5 | 6 | class SvelteDirect { 7 | 8 | /** 9 | * Constructor 10 | */ 11 | constructor() { 12 | this.options = { 13 | componentMode: true, 14 | loaderOptions: { 15 | dev: !Mix.inProduction(), 16 | compilerOptions: { 17 | customElement: false 18 | } 19 | } 20 | }; 21 | 22 | this.manifest = []; 23 | this.tempJavascriptBootloaderFiles = []; 24 | } 25 | 26 | /** 27 | * Dependencies for Svelte webpack 28 | */ 29 | dependencies() { 30 | this.requiresReload = true; 31 | return ["svelte", "svelte-loader"]; 32 | } 33 | 34 | /** 35 | * Plugin entry point 36 | * 37 | * @param {string} inputPath 38 | * @param {string} outputPath 39 | * @param {object} options 40 | */ 41 | register(inputPath, outputPath, options) 42 | { 43 | this.options = { ...this.options, ...options}; 44 | 45 | this.options.loaderOptions.compilerOptions.customElement = !this.options.componentMode; 46 | 47 | this.handle(inputPath, outputPath); 48 | } 49 | 50 | /** 51 | * Webpack rules for building Svelte files 52 | */ 53 | webpackRules() { 54 | return [ 55 | { 56 | test: /\.(html|svelte)$/, 57 | use: [ 58 | { loader: 'babel-loader', options: Config.babel() }, 59 | { loader: 'svelte-loader', options: this.options.loaderOptions } 60 | ] 61 | }, 62 | { 63 | test: /\.(mjs)$/, 64 | use: { loader: 'babel-loader', options: Config.babel() } 65 | } 66 | ]; 67 | } 68 | 69 | /** 70 | * Prepare the provided path for processing. 71 | * 72 | * @param {object} webpackConfig 73 | */ 74 | webpackConfig(webpackConfig) { 75 | webpackConfig.resolve.mainFields = [ 76 | 'svelte', 77 | 'browser', 78 | 'module', 79 | 'main', 80 | ]; 81 | webpackConfig.resolve.extensions = ['.mjs', '.js', '.svelte']; 82 | webpackConfig.resolve.alias = webpackConfig.resolve.alias || {}; 83 | webpackConfig.resolve.alias.svelte = resolve( 84 | 'node_modules', 85 | 'svelte' 86 | ); 87 | } 88 | 89 | /** 90 | * Import our dependencies 91 | */ 92 | boot() { 93 | let svelte = require("svelte"); 94 | let loader = require("svelte-loader"); 95 | } 96 | 97 | /** 98 | * Main Plugin logic 99 | * 100 | * Load config, create temp bootloaders, add them to mix 101 | * 102 | * @param {string} inputPath 103 | * @param {string} outputPath 104 | */ 105 | handle(inputPath, outputPath) 106 | { 107 | let enableSvelteComponentMode =this.options.componentMode; 108 | 109 | mix.before(async () => { 110 | try { 111 | await this.createSvelteBootloaders(inputPath, outputPath, enableSvelteComponentMode); 112 | } catch (error) { 113 | console.error('[SvelteDirect] Encountered error...'); 114 | await this.cleanUp(); 115 | throw error; 116 | } 117 | }); 118 | 119 | mix.after(async () => { 120 | await this.writeManifest(); 121 | await this.cleanUp(); 122 | }); 123 | } 124 | 125 | /** 126 | * Write a manifest file that our Blade pre-compiler uses 127 | * to determine which maps to which compiled JS file 128 | * 129 | * @todo make this an option via config 130 | */ 131 | async writeManifest() 132 | { 133 | const bootstrapFile = resolve( 'bootstrap', 'cache') + '/svelte-direct-components.php'; 134 | let phpCode = ' previous + `'${current.tag}' => '${current.filename}', ` 138 | , '' 139 | ) 140 | 141 | phpCode = phpCode + arrayContent + '];'; 142 | 143 | await writeFile(bootstrapFile, phpCode, 'utf8'); 144 | } 145 | 146 | /** 147 | * Cleanup our generated bootstrap JS files 148 | */ 149 | async cleanUp() 150 | { 151 | for (const f of this.tempJavascriptBootloaderFiles) { 152 | const compiledFilename = f.replace('.svelte', '.js'); 153 | //await unlink(compiledFilename); 154 | } 155 | } 156 | 157 | /** 158 | * Locate all Svelte files in the given path 159 | * 160 | * @param {string} dir 161 | */ 162 | async* fetchSvelteFiles(dir) { 163 | const dirents = await readdir(dir, { withFileTypes: true }); 164 | for (const dirent of dirents) { 165 | const res = resolve(dir, dirent.name); 166 | if (dirent.isDirectory()) { 167 | yield* this.fetchSvelteFiles(res); 168 | } else if (res.toLowerCase().indexOf('.svelte') !== -1) { 169 | yield res; 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * Generate bootstrap JS files that load our Svelte components 176 | * 177 | * @param {string} inputPath 178 | * @param {string} outputPath 179 | * @param {boolean} enableSvelteComponentMode 180 | */ 181 | async createSvelteBootloaders(inputPath, outputPath, enableSvelteComponentMode) { 182 | this.tempJavascriptBootloaderFiles = []; 183 | 184 | for await (const f of this.fetchSvelteFiles(inputPath)) { 185 | const compiledFilename = f.replace('.svelte', '.js'); 186 | const svelteAppData = enableSvelteComponentMode 187 | ? await this.generateBootstrapSvelteComponent(f) 188 | : await this.generateBootstrapWebComponent(f); 189 | 190 | await writeFile(compiledFilename, svelteAppData.code, 'utf8'); 191 | 192 | mix.js(compiledFilename, outputPath); 193 | 194 | this.manifest.push({ 195 | tag: svelteAppData.tag, 196 | filename: this.normalizePath(outputPath) + '/' + basename(compiledFilename) 197 | }) 198 | 199 | this.tempJavascriptBootloaderFiles.push(compiledFilename); 200 | } 201 | }; 202 | 203 | /** 204 | * Prepare the provided path for processing. 205 | * 206 | * Stolen from Laravel Mix to make sure we were compatible 207 | * 208 | * @param {string} filePath 209 | */ 210 | normalizePath(filePath) { 211 | if ( 212 | Mix.config.publicPath && 213 | filePath.startsWith(Mix.config.publicPath) 214 | ) { 215 | filePath = filePath.substring(Mix.config.publicPath.length); 216 | } 217 | filePath = filePath.replace(/\\/g, '/'); 218 | 219 | if (!filePath.startsWith('/')) { 220 | filePath = '/' + filePath; 221 | } 222 | 223 | return filePath; 224 | } 225 | 226 | /** 227 | * Generate bootstrap JS code for Svelte Component 228 | * 229 | * Using WebComponents/Svelte (customElement:true) 230 | * 231 | * @param {string} svelteComponentPath 232 | */ 233 | async generateBootstrapWebComponent(svelteComponentPath) 234 | { 235 | let svelteCode = await readFile(svelteComponentPath).then(); 236 | let svelteTagName = findSvelteTagName(svelteCode); 237 | 238 | if (!svelteTagName) { 239 | throw '[SvelteDirect] Cannot Determine Tag Name In: ' + svelteComponentPath 240 | } 241 | 242 | return { 243 | code: 'export { default as App } from "./' + basename(svelteComponentPath) + '";', 244 | tag: svelteTagName 245 | }; 246 | } 247 | 248 | /** 249 | * Generate bootstrap JS code for Svelte Component 250 | * 251 | * Using Standard Svelte Component (customElement: false) 252 | * 253 | * @param {string} svelteComponentPath 254 | */ 255 | async generateBootstrapSvelteComponent(svelteComponentPath) 256 | { 257 | let svelteCode = await readFile(svelteComponentPath).then(); 258 | let svelteProps = findSvelteProps(svelteCode); 259 | let svelteTagName = findSvelteTagName(svelteCode); 260 | 261 | if (!svelteTagName) { 262 | throw '[SvelteDirect] Cannot Determine Tag Name In: ' + svelteComponentPath 263 | } 264 | 265 | return { 266 | code: ` 267 | 268 | // Generated By SvelteDirect 269 | 270 | import component from "svelte-tag"; 271 | import App from "./${basename(svelteComponentPath)}" 272 | const props = JSON.parse('${JSON.stringify(svelteProps)}'); 273 | 274 | new component({component:App,tagname:"${svelteTagName}",attributes: props, shadow: false}) 275 | 276 | `, 277 | tag: svelteTagName 278 | }; 279 | } 280 | } 281 | 282 | mix.extend("svelteDirect", new SvelteDirect()); 283 | --------------------------------------------------------------------------------