diff --git a/Dockerfile b/Dockerfile index 62d3b99c..072472ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.19.0 +FROM node:18 LABEL maintainer="froala_git_travis_bot@idera.com" diff --git a/Readme.md b/Readme.md index 8aad8273..500eb7a6 100644 --- a/Readme.md +++ b/Readme.md @@ -21,13 +21,13 @@ npm update froala-editor npm install font-awesome --save ``` -## Usage with Class Component +## Usage with Class Component #### 1. Require and use Froala Editor component inside your application. ```jsx import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Require Editor CSS files. @@ -63,7 +63,8 @@ import FroalaEditorComponent from 'react-froala-wysiwyg'; // import FroalaEditorInput from 'react-froala-wysiwyg/FroalaEditorInput'; // Render Froala Editor component. -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); ``` #### Add editor to UI by passing id to html element @@ -244,13 +245,13 @@ To display content created with the froala editor use the `FroalaEditorView` com ``` -## Usage with Functional Component +## Usage with Functional Component #### 1. Require and use Froala Editor component inside your application. ```jsx import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Require Editor CSS files. import 'froala-editor/css/froala_style.min.css'; @@ -369,13 +370,13 @@ import React,{ useState } from 'react'; const App=()=> { const [model,setModel] = useState("Example Set"); - + const handleModelChange= (event)=>{ setModel(event) } return (
- @@ -419,7 +420,7 @@ The model must be an object containing the attributes for your special tags. Exa model={{src: 'path/to/image.jpg', width:"300px", alt:"Old Clock" - }} + }} ``` * The model can contain a special attribute named **innerHTML** which inserts innerHTML in the element: If you are using 'button' tag, you can specify the button text like this: @@ -429,6 +430,84 @@ model={{innerHTML: 'Click Me'}} ``` As the button text is modified by the editor, the **innerHTML** attribute from buttonModel model will be modified too. +## Usage with Server-Side Rendering + +When using Server-Side Rendering you can use Froala components as described previously. If you require additonal plugins or translations, you will have to use React lazy loading +in order to load the plugins first. Please refer to the examples below. + +### Class component + +```js +import React, { Suspense } from "react"; + +import 'froala-editor/css/froala_editor.pkgd.min.css'; +import 'froala-editor/css/froala_style.css'; + +const FroalaEditor = React.lazy(() => import('react-froala-wysiwyg')); + +export default class MyComponent extends React.Component { + constructor () { + super(); + this.state = { + isInitialized: false + }; + } + + componentDidMount() { + // Import all Froala Editor plugins; + import('froala-editor/js/plugins.pkgd.min.js').then(() => this.setState({isInitialized: true})); + } + + render() { + return <> + {this.state.isInitialized && ( + Loading...

}> + +
+ )} + ; + } +} +``` + +### Functional component + +```js +import React, { Suspense, useEffect, useState } from "react"; + +import 'froala-editor/css/froala_editor.pkgd.min.css'; +import 'froala-editor/css/froala_style.css'; + +const FroalaEditor = React.lazy(() => import('react-froala-wysiwyg')); + +export default function MyComponent() { + const [isInitialized, setIsInitialized] = useState(false); + + useEffect(() => { + async function initPlugins() { + // Import all Froala Editor plugins; + await import('froala-editor/js/plugins.pkgd.min.js'); + setIsInitialized(true); + } + if (!isInitialized) { + initPlugins(); + } + }); + + return <> + {isInitialized && ( + Loading...

}> + +
+ )} + ; +} +``` + ## Manual Instantiation Gets the functionality to operate on the editor: create, destroy and get editor instance. Use it if you want to manually initialize the editor. @@ -519,7 +598,7 @@ Froalaeditor.DefineIcon('alert', {NAME: 'info', SVG_KEY: 'help'}); } }); - + ``` Now you can use these buttons in options: ```javascript diff --git a/demo/src/basic.jsx b/demo/src/basic.jsx index 58b1abaf..71e7fc95 100644 --- a/demo/src/basic.jsx +++ b/demo/src/basic.jsx @@ -6,10 +6,11 @@ import 'file-loader?name=[name].[ext]!./basic.html'; import FroalaEditor from 'react-froala-wysiwyg'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. -ReactDOM.render(, document.getElementById('editor')); +}} />); diff --git a/demo/src/custombutton.jsx b/demo/src/custombutton.jsx index 549c1892..eb2ab356 100644 --- a/demo/src/custombutton.jsx +++ b/demo/src/custombutton.jsx @@ -4,7 +4,7 @@ import 'file-loader?name=[name].[ext]!./custombutton.html'; import FroalaEditor from 'react-froala-wysiwyg'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import Froalaeditor from 'froala-editor'; Froalaeditor.DefineIcon('alert', {NAME: 'info', SVG_KEY: 'help'}); Froalaeditor.RegisterCommand('alert', { @@ -41,4 +41,5 @@ Froalaeditor.DefineIcon('alert', {NAME: 'info', SVG_KEY: 'help'}); }); // Render Froala Editor component. -ReactDOM.render(, document.getElementById('editor')); \ No newline at end of file +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); \ No newline at end of file diff --git a/demo/src/edit_inline.jsx b/demo/src/edit_inline.jsx index aaf56bfd..7ebe277b 100644 --- a/demo/src/edit_inline.jsx +++ b/demo/src/edit_inline.jsx @@ -4,7 +4,7 @@ import 'froala-editor/css/froala_style.css'; import FroalaEditor from 'react-froala-wysiwyg'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -58,6 +58,7 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); require("file-loader?name=[name].[ext]!./edit_inline.html"); \ No newline at end of file diff --git a/demo/src/full_editor.jsx b/demo/src/full_editor.jsx index 54bed433..7391c927 100644 --- a/demo/src/full_editor.jsx +++ b/demo/src/full_editor.jsx @@ -7,7 +7,7 @@ import 'froala-editor/js/plugins.pkgd.min.js'; import FroalaEditor from 'react-froala-wysiwyg'; import FroalaEditorView from 'react-froala-wysiwyg/FroalaEditorView'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -45,5 +45,6 @@ class EditorComponent extends React.Component { } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); diff --git a/demo/src/init_on_button.jsx b/demo/src/init_on_button.jsx index 6090300b..b53204dd 100644 --- a/demo/src/init_on_button.jsx +++ b/demo/src/init_on_button.jsx @@ -5,7 +5,7 @@ import 'file-loader?name=[name].[ext]!./init_on_button.html'; import 'froala-editor/js/plugins.pkgd.min.js'; import FroalaEditorButton from 'react-froala-wysiwyg/FroalaEditorButton'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -43,5 +43,6 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); diff --git a/demo/src/init_on_image.jsx b/demo/src/init_on_image.jsx index b1595203..4cae5432 100644 --- a/demo/src/init_on_image.jsx +++ b/demo/src/init_on_image.jsx @@ -8,7 +8,7 @@ import 'file-loader?name=[name].[ext]!./init_on_image.html'; import FroalaEditorImg from 'react-froala-wysiwyg/FroalaEditorImg'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -57,5 +57,6 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); diff --git a/demo/src/init_on_input.jsx b/demo/src/init_on_input.jsx index 3da4792b..d3146d91 100644 --- a/demo/src/init_on_input.jsx +++ b/demo/src/init_on_input.jsx @@ -7,7 +7,7 @@ import 'file-loader?name=[name].[ext]!./init_on_input.html'; import FroalaEditorInput from 'react-froala-wysiwyg/FroalaEditorInput'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -44,5 +44,6 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); diff --git a/demo/src/init_on_link.jsx b/demo/src/init_on_link.jsx index 91ae1b81..db731ae6 100644 --- a/demo/src/init_on_link.jsx +++ b/demo/src/init_on_link.jsx @@ -7,7 +7,7 @@ import 'froala-editor/js/plugins.pkgd.min.js'; import FroalaEditorA from 'react-froala-wysiwyg/FroalaEditorA'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -47,6 +47,7 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); import "file-loader?name=[name].[ext]!./init_on_link.html"; \ No newline at end of file diff --git a/demo/src/manual_initialization.jsx b/demo/src/manual_initialization.jsx index 84ad3912..e190e54d 100644 --- a/demo/src/manual_initialization.jsx +++ b/demo/src/manual_initialization.jsx @@ -6,7 +6,7 @@ import 'file-loader?name=[name].[ext]!./manual_initialization.html'; import FroalaEditor from 'react-froala-wysiwyg'; import FroalaEditorView from 'react-froala-wysiwyg/FroalaEditorView'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -80,5 +80,6 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); diff --git a/demo/src/two_way_binding.jsx b/demo/src/two_way_binding.jsx index f882ae0a..be119c11 100644 --- a/demo/src/two_way_binding.jsx +++ b/demo/src/two_way_binding.jsx @@ -6,7 +6,7 @@ import 'file-loader?name=[name].[ext]!./two_way_binding.html'; import FroalaEditor from 'react-froala-wysiwyg'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; // Render Froala Editor component. class EditorComponent extends React.Component { @@ -43,5 +43,6 @@ class EditorComponent extends React.Component { } } -ReactDOM.render(, document.getElementById('editor')); +const root = ReactDOM.createRoot(document.getElementById('editor')); +root.render(); diff --git a/demo/webpack.config.js b/demo/webpack.config.js index efa8d58c..abd8ccc8 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -12,7 +12,7 @@ module.exports = { init_on_button: './src/init_on_button.jsx', init_on_link: './src/init_on_link.jsx', init_on_input: './src/init_on_input.jsx', - custombutton: './src/custombutton.jsx' + custombutton: './src/custombutton.jsx' }, optimization: { @@ -29,9 +29,10 @@ module.exports = { cacheDirectory: true, presets: [ ['@babel/preset-env', { - 'targets': { + 'targets': { "ie": "11" }, + "corejs": 3, "useBuiltIns": "entry" }], '@babel/preset-react'] @@ -41,7 +42,12 @@ module.exports = { test: /\.css$/, use: [ 'style-loader', - 'css-loader' + { + loader: 'css-loader', + options: { + esModule: false + } + } ] }, { @@ -81,6 +87,11 @@ module.exports = { }), - new CopyWebpackPlugin([{ from: './src/index.html'}, {from: './src/image.jpg'} ]) + new CopyWebpackPlugin({ + patterns: [ + { from: './src/index.html' }, + { from: './src/image.jpg' } + ] + }) ] }; \ No newline at end of file diff --git a/lib/FroalaEditor.jsx b/lib/FroalaEditor.jsx index c1f8f95d..3ece72d8 100644 --- a/lib/FroalaEditor.jsx +++ b/lib/FroalaEditor.jsx @@ -1,3 +1,5 @@ +'use client'; + import FroalaEditorFunctionality from './FroalaEditorFunctionality.jsx'; import React from 'react'; diff --git a/lib/FroalaEditorA.jsx b/lib/FroalaEditorA.jsx index 10cc42c9..2f128261 100644 --- a/lib/FroalaEditorA.jsx +++ b/lib/FroalaEditorA.jsx @@ -1,3 +1,5 @@ +'use client'; + import FroalaEditorFunctionality from './FroalaEditorFunctionality.jsx'; import React from 'react'; diff --git a/lib/FroalaEditorButton.jsx b/lib/FroalaEditorButton.jsx index 35526c34..df284c34 100644 --- a/lib/FroalaEditorButton.jsx +++ b/lib/FroalaEditorButton.jsx @@ -1,3 +1,5 @@ +'use client'; + import FroalaEditorFunctionality from './FroalaEditorFunctionality.jsx'; import React from 'react'; diff --git a/lib/FroalaEditorFunctionality.jsx b/lib/FroalaEditorFunctionality.jsx index b53ee2e4..fa4adc2a 100644 --- a/lib/FroalaEditorFunctionality.jsx +++ b/lib/FroalaEditorFunctionality.jsx @@ -1,7 +1,9 @@ -import FroalaEditor from 'froala-editor'; +'use client'; + import React from 'react'; let lastId = 0; +let FroalaEditor = null; export default class FroalaEditorFunctionality extends React.Component { constructor(props) { super(props); @@ -24,6 +26,7 @@ export default class FroalaEditorFunctionality extends React.Component { }; this.editorInitialized = false; + this.editorCreated = false; this.SPECIAL_TAGS = ['img', 'button', 'input', 'a']; this.INNER_HTML_ATTR = 'innerHTML'; @@ -34,17 +37,21 @@ export default class FroalaEditorFunctionality extends React.Component { // After first time render. componentDidMount() { - let tagName = this.el.tagName.toLowerCase(); - if (this.SPECIAL_TAGS.indexOf(tagName) != -1) { - this.tag = tagName; - this.hasSpecialTag = true; - } + import(/*webpackIgnore: true*/ 'froala-editor').then((module) => { + FroalaEditor = module.default; - if (this.props.onManualControllerReady) { - this.generateManualController(); - } else { - this.createEditor(); - } + let tagName = this.el.tagName.toLowerCase(); + if (this.SPECIAL_TAGS.indexOf(tagName) != -1) { + this.tag = tagName; + this.hasSpecialTag = true; + } + + if (this.props.onManualControllerReady) { + this.generateManualController(); + } else { + this.createEditor(); + } + }); } componentWillUnmount() { @@ -61,10 +68,10 @@ export default class FroalaEditorFunctionality extends React.Component { // Return cloned object clone(item) { - const me = this; + const me = this; if (!item) { return item; } // null, undefined values check - let types = [ Number, String, Boolean ], + let types = [ Number, String, Boolean ], result; // normalizing primitives if someone did new String('aaa'), or new Number('444'); @@ -77,13 +84,13 @@ export default class FroalaEditorFunctionality extends React.Component { if (typeof result == "undefined") { if (Object.prototype.toString.call( item ) === "[object Array]") { result = []; - item.forEach(function(child, index, array) { + item.forEach(function(child, index, array) { result[index] = me.clone( child ); }); } else if (typeof item == "object") { // testing that this is DOM if (item.nodeType && typeof item.cloneNode == "function") { - result = item.cloneNode( true ); + result = item.cloneNode( true ); } else if (!item.prototype) { // check that this is a literal if (item instanceof Date) { result = new Date(item); @@ -107,9 +114,9 @@ export default class FroalaEditorFunctionality extends React.Component { } return result; } - + createEditor() { - if (this.editorInitialized) { + if (this.editorInitialized || this.editorCreated) { return; } @@ -128,6 +135,7 @@ export default class FroalaEditorFunctionality extends React.Component { this.config.events.initialized = () => this.initListeners(); this.editor = new FroalaEditor(this.element, this.config); + this.editorCreated = true; } setContent(firstTime) { @@ -200,6 +208,7 @@ export default class FroalaEditorFunctionality extends React.Component { this.listeningEvents.length = 0; this.element = null; this.editorInitialized = false; + this.editorCreated = false; this.config = { immediateReactModelUpdate: false, reactIgnoreAttrs: null @@ -280,7 +289,7 @@ export default class FroalaEditorFunctionality extends React.Component { self.updateModel(); }); } - + if (this.config.immediateReactModelUpdate) { this.editor.events.on('keyup', function () { self.updateModel(); diff --git a/lib/FroalaEditorImg.jsx b/lib/FroalaEditorImg.jsx index 75339aba..30a5c544 100644 --- a/lib/FroalaEditorImg.jsx +++ b/lib/FroalaEditorImg.jsx @@ -1,3 +1,5 @@ +'use client'; + import FroalaEditorFunctionality from './FroalaEditorFunctionality.jsx'; import React from 'react'; diff --git a/lib/FroalaEditorInput.jsx b/lib/FroalaEditorInput.jsx index 5810275a..4a06c155 100644 --- a/lib/FroalaEditorInput.jsx +++ b/lib/FroalaEditorInput.jsx @@ -1,3 +1,5 @@ +'use client'; + import FroalaEditorFunctionality from './FroalaEditorFunctionality.jsx'; import React from 'react'; diff --git a/lib/FroalaEditorView.jsx b/lib/FroalaEditorView.jsx index 84026566..c4602940 100644 --- a/lib/FroalaEditorView.jsx +++ b/lib/FroalaEditorView.jsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; export default class FroalaEditorView extends React.Component { diff --git a/package.json b/package.json index 0ea89418..c7c7b9b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-froala-wysiwyg", - "version": "4.3.1", + "version": "4.4.0", "description": "React component for Froala WYSIWYG HTML rich text editor.", "main": "index.js", "types": "./lib/index.d.ts", @@ -22,52 +22,44 @@ "react-dom": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { - "create-react-class": "^15.5.2", - "froala-editor": "4.3.1" + "froala-editor": "4.4.0" }, "devDependencies": { - "@babel/cli": "^7.0.0", - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-decorators": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/polyfill": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/preset-react": "^7.12.13", - "autoprefixer": "^8.3.0", + "@babel/cli": "^7.25.9", + "@babel/core": "^7.26.0", + "@babel/plugin-proposal-decorators": "^7.25.9", + "@babel/plugin-proposal-function-sent": "^7.25.9", + "@babel/plugin-proposal-throw-expressions": "^7.25.9", + "@babel/preset-env": "^7.26.0", + "@babel/preset-react": "^7.25.9", + "autoprefixer": "^10.4.20", "babel-loader": "^8.0.0", - "copy-webpack-plugin": "^4.5.1", - "css-loader": "^0.28.11", + "copy-webpack-plugin": "^12.0.2", + "core-js": "^3.39.0", + "css-loader": "^7.1.2", "exports-loader": "^0.6.2", - "file-loader": "^1.1.11", - "highlight.js": "^9.3.0", - "imports-loader": "^0.6.4", - "jsdom": "^9.0.0", - "mocha": "^3.0.0", + "file-loader": "^6.2.0", + "highlight.js": "^11.10.0", + "imports-loader": "^5.0.0", + "jsdom": "^25.0.1", + "mocha": "^10.8.2", "mock-require": "^1.3.0", - "nightwatch": "^0.9.6", - "postcss-loader": "^2.1.4", + "nightwatch": "^3.9.0", + "postcss-loader": "^8.1.1", "raw-loader": "^0.5.1", - "react": "^16.0.0", - "react-addons-test-utils": "^15.0.0", - "react-dom": "^16.0.0", - "react-highlight": "^0.9.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-highlight": "^0.15.0", "sinon": "^1.17.4", "style-loader": "^0.21.0", - "url-loader": "^0.5.5", - "webpack": "^4.32.1", - "webpack-cli": "^3.3.0", - "webpack-dev-server": "^3.3.0" + "url-loader": "^4.1.1", + "webpack": "^5.96.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" }, "scripts": { - "build": "webpack-cli && webpack-cli -p", - "demo": "cd demo && webpack-cli && \"../node_modules/.bin/webpack-dev-server\" --disableHostCheck=true --port 4000 --host 0.0.0.0 --content-base dist/", + "build": "webpack-cli --mode=development && webpack-cli --mode=production", + "demo": "cd demo && webpack-cli --mode=development && \"../node_modules/.bin/webpack-dev-server\" --mode development --allowed-hosts all --port 4000 --host 0.0.0.0 --static dist/", "prepublishOnly": "npm run build && bash lib/copy_bundles.sh" }, "repository": { diff --git a/webpack.config.js b/webpack.config.js index ea3dc27f..aaf3af5d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,12 +1,5 @@ var path = require('path'); -/** - * If -p flag is set, minify the files - * @type {boolean} - */ -var src = (process.argv.indexOf('-p') === -1); -var filenamePostfix = src ? '.src' : ''; - var froalaExternals = { 'froala-editor': { root: 'froala-editor', @@ -33,7 +26,7 @@ var reactExternals = { var externals = [reactExternals,'froala-editor']; -module.exports = { +var config = { entry: { // Array syntax to workaround https://github.com/webpack/webpack/issues/300 'index': ['./lib/FroalaEditor.jsx'], @@ -55,9 +48,10 @@ module.exports = { cacheDirectory: true, presets: [ ['@babel/preset-env', { - 'targets': { + 'targets': { "ie": "11" }, + "corejs": 3, "useBuiltIns": "entry" }], '@babel/preset-react'] @@ -72,8 +66,17 @@ module.exports = { modules: ['./node_modules'] }, output: { - filename: '[name]' + filenamePostfix + '.js', + globalObject: 'this', + publicPath: '', + filename: '[name].js', libraryTarget: 'umd', library: '[name]' } -}; \ No newline at end of file +}; + +module.exports = (env, argv) => { + if (argv.mode === 'development') { + config.output.filename = '[name].src.js'; + } + return config; +} \ No newline at end of file