From a65ecfa4427f7715ce90f49dd87daab6f509ea11 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Sun, 28 Aug 2016 21:46:10 +0300 Subject: [PATCH] =?UTF-8?q?Move=20to=20webpack=20+=20AoT=20compilation=20?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 10 +- .travis.yml | 9 +- build/helpers.js | 9 + build/paths.js | 8 +- build/tasks/build.js | 165 --- build/tasks/clean.js | 9 - build/tasks/e2e.js | 34 - build/tasks/lint.js | 15 - build/tasks/serve.js | 17 - build/tasks/test.js | 52 - build/tasks/watch.js | 14 - build/webpack.dev.js | 98 ++ build/webpack.prod.js | 82 ++ build/webpack.test.js | 100 ++ demo/index.html | 5 +- demo/main.js | 1 - karma.conf.js | 108 +- lib/bootstrap.dev.ts | 6 + lib/bootstrap.ts | 6 + lib/components/ApiInfo/api-info.html | 2 +- lib/components/ApiInfo/api-info.ts | 10 +- lib/components/ApiLogo/api-logo.ts | 8 +- ...a-common.scss => _json-schema-common.scss} | 0 lib/components/JsonSchema/json-schema.ts | 8 +- lib/components/Method/method.ts | 8 +- lib/components/MethodsList/methods-list.ts | 8 +- lib/components/ParamsList/params-list.ts | 8 +- lib/components/Redoc/redoc.ts | 24 +- .../RequestSamples/request-samples.ts | 8 +- .../ResponsesList/responses-list.ts | 8 +- .../ResponsesSamples/responses-samples.ts | 8 +- lib/components/SchemaSample/schema-sample.ts | 8 +- lib/components/SideMenu/side-menu.spec.ts | 2 +- lib/components/SideMenu/side-menu.ts | 31 +- lib/components/Warnings/warnings.ts | 8 +- lib/components/base.ts | 4 + lib/index-dev.ts | 59 ++ lib/index.ts | 39 +- lib/polyfills.ts | 4 + lib/redoc.module.ts | 4 +- lib/services/hash.service.spec.ts | 4 +- lib/services/hash.service.ts | 8 +- lib/services/options.service.spec.ts | 3 +- lib/services/options.service.ts | 12 +- lib/services/schema-helper.service.ts | 2 +- lib/services/scroll.service.ts | 7 +- lib/shared/components/DropDown/drop-down.ts | 2 +- .../StickySidebar/sticky-sidebar.ts | 24 +- lib/utils/JsonPointer.ts | 2 +- lib/utils/SpecManager.ts | 6 +- lib/utils/browser-adapter.ts | 73 ++ lib/utils/helpers.js | 103 ++ lib/utils/helpers.ts | 2 +- lib/utils/prismjs-bundle.ts | 0 lib/vendor.ts | 31 + manual-types/index.d.ts | 5 +- package.json | 126 +-- protractor.conf.js | 6 +- system.config.js | 948 ------------------ tests/e2e/.gitignore | 1 + tests/e2e/index.html | 3 +- tests/e2e/{redoc.spec.js => redoc.e2e.js} | 0 tests/setup.ts | 33 - tests/spec-bundle.js | 66 ++ tsconfig.json | 25 +- 65 files changed, 917 insertions(+), 1582 deletions(-) create mode 100644 build/helpers.js delete mode 100644 build/tasks/build.js delete mode 100644 build/tasks/clean.js delete mode 100644 build/tasks/e2e.js delete mode 100644 build/tasks/lint.js delete mode 100644 build/tasks/serve.js delete mode 100644 build/tasks/test.js delete mode 100644 build/tasks/watch.js create mode 100644 build/webpack.dev.js create mode 100644 build/webpack.prod.js create mode 100644 build/webpack.test.js create mode 100644 lib/bootstrap.dev.ts create mode 100644 lib/bootstrap.ts rename lib/components/JsonSchema/{json-schema-common.scss => _json-schema-common.scss} (100%) create mode 100644 lib/index-dev.ts create mode 100644 lib/polyfills.ts create mode 100644 lib/utils/browser-adapter.ts create mode 100644 lib/utils/helpers.js delete mode 100644 lib/utils/prismjs-bundle.ts create mode 100644 lib/vendor.ts delete mode 100644 system.config.js create mode 100644 tests/e2e/.gitignore rename tests/e2e/{redoc.spec.js => redoc.e2e.js} (100%) delete mode 100644 tests/setup.ts create mode 100644 tests/spec-bundle.js diff --git a/.gitignore b/.gitignore index e8d87581..82cb0860 100644 --- a/.gitignore +++ b/.gitignore @@ -20,11 +20,17 @@ npm-debug.log* # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules -#jspm -jspm_packages +# compiled css +lib/**/*.css +# files produced by ngc +lib/**/*.ngfactory.ts +lib/**/*.css.shim.ts + +# other /dist /demo/build .tmp /coverage .ghpages-tmp +stats.json diff --git a/.travis.yml b/.travis.yml index 31d1a1e5..74e598aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,12 +25,11 @@ addons: cache: directories: - node_modules - - jspm_packages -before_install: -- travis_retry npm install jspm -- jspm config registries.github.auth $JSPM_GITHUB_AUTH_TOKEN before_script: -- npm run jspm-install + - npm run e2e-server > /dev/null & # kill e2e server + - sleep 3 # give server time to start +after_script: + - kill %1 # kill e2e server before_deploy: - npm run build-dist deploy: diff --git a/build/helpers.js b/build/helpers.js new file mode 100644 index 00000000..25bd2532 --- /dev/null +++ b/build/helpers.js @@ -0,0 +1,9 @@ +const path = require('path'); +function root(args) { + args = Array.prototype.slice.call(arguments, 0); + return path.join.apply(path, [__dirname, '..'].concat(args)); +} + +module.exports = { + root: root +} diff --git a/build/paths.js b/build/paths.js index 63a59b59..2707c00c 100644 --- a/build/paths.js +++ b/build/paths.js @@ -1,15 +1,9 @@ var path = require('path'); var paths = { - source: 'lib/**/*.ts', - html: 'lib/**/*.html', - scss: 'lib/**/*.scss', - sourceEntryPoint: 'lib/index.js', - outputName: 'redoc', + outputName: 'redoc.min.js', output: 'dist/', - tmp: '.tmp/', demo: 'demo/**/*', - tests: '{lib,tests}/**/*.spec.ts', releases: 'demo/releases/' } diff --git a/build/tasks/build.js b/build/tasks/build.js deleted file mode 100644 index 39d51162..00000000 --- a/build/tasks/build.js +++ /dev/null @@ -1,165 +0,0 @@ -var gulp = require('gulp'); -var runSequence = require('run-sequence'); -var Builder = require('systemjs-builder'); -var inlineNg2Template = require('gulp-inline-ng2-template'); -var path = require('path'); -var sourcemaps = require('gulp-sourcemaps'); -var paths = require('../paths'); -var fs= require('fs'); -var concat = require('gulp-concat'); -var gulp = require('gulp'); -var sass = require('gulp-sass'); -var replace = require('gulp-replace'); -var rename = require('gulp-rename'); -var argv = require('yargs').argv; -var gulpIf = require('gulp-if'); -var sassCopm = require('node-sass'); -var ts = require('typescript'); - -gulp.task('build', function (callback) { - if (argv.skipRebuild) { - console.log('>>> Rebuild skipped') - return callback(); - } - return runSequence( - 'clean', - 'transpile', - 'bundle', - 'concatDeps', - 'copyDebug', - callback - ); -}); - -gulp.task('transpile', function(cb) { - return runSequence( - 'tsc', - 'inlineTemplates', - cb - ); -}); - -gulp.task('copyDebug', () => { - if (!argv.prod) { - // copy for be accessible from demo for debug - cp(paths.redocBuilt + '.js', paths.redocBuilt + '.min.js'); - } -}); - -gulp.task('tsc', function() { - exec('tsc -v'); - exec('tsc -p ./tsconfig.json'); -}); - -gulp.task('inlineTemplates', ['sass'], function() { - return gulp.src('.tmp/**/*.js', { base: './tmp' }) - .pipe(replace(/'(.*?)\.css'/g, '\'$1.scss\'')) - .pipe(inlineNg2Template({ - base: './', - useRelativePaths: true, - styleProcessor: compileSass, - customFilePath: function(ext, file) { - var cwd = process.cwd(); - var relative = path.relative(cwd, file); - relative = relative.substring('5'); - return path.join(cwd, relative); - } - })) - .pipe(gulp.dest(paths.tmp)); -}); - -function compileSass(ext, file, cb) { - file = file.replace('../../shared/styles/variables', 'lib/shared/styles/variables'); - file = file.replace('json-schema-common', 'lib/components/JsonSchema/json-schema-common'); - file = file.replace('../../shared/styles/share-link', 'lib/shared/styles/share-link'); - file = file.replace('../JsonSchema/lib/components/JsonSchema/json-schema-common', 'lib/components/JsonSchema/json-schema-common'); - file = file.replace('../../styles/variables', 'lib/shared/styles/variables'); - - cb(null, sassCopm.renderSync({data: file}).css); -} - -var JS_DEPS = argv.prod ? [ - 'lib/utils/browser-update.js', - 'node_modules/zone.js/dist/zone.min.js', - 'node_modules/reflect-metadata/Reflect.js', - 'node_modules/babel-polyfill/dist/polyfill.min.js' -]: [ - 'lib/utils/browser-update.js', - 'node_modules/zone.js/dist/zone.js', - 'node_modules/zone.js/dist/long-stack-trace-zone.js', - 'node_modules/reflect-metadata/Reflect.js', - 'node_modules/babel-polyfill/dist/polyfill.js' -]; - -var outputFileName = paths.redocBuilt + (argv.prod ? '.min.js' : '.js'); - -gulp.task('sass', function () { - return gulp.src(paths.scss, { base: './' }) - .pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError)) - .pipe(gulp.dest(paths.tmp)); -}); - -// concatenate angular2 deps -gulp.task('concatDeps', ['concatPrism'], function() { - return gulp.src(JS_DEPS.concat([path.join(paths.tmp, 'prismjs-bundle.js'), outputFileName])) - .pipe(gulpIf(!argv.prod, sourcemaps.init({loadMaps: true}))) - .pipe(concat(outputFileName)) - .pipe(gulpIf(!argv.prod, sourcemaps.write('.'))) - .pipe(gulp.dest('.')) -}); - -gulp.task('bundle', ['injectVersionFile'], function bundle(done) { - mkdir('-p', 'dist'); - cp('lib/index.js', path.join(paths.tmp, paths.sourceEntryPoint)); - var builder = new Builder('./', 'system.config.js'); - - builder - .buildStatic(path.join(paths.tmp, paths.sourceEntryPoint), - outputFileName, - { format:'umd', sourceMaps: !argv.prod, lowResSourceMaps: true, minify: argv.prod, mangle: false, globalName: 'Redoc' } - ) - .then(() => { - // wait some time to allow flush - setTimeout(() => done(), 500); - }) - .catch(err => done(err)); -}); - -gulp.task('concatPrism', function() { - require('../../system.config.js'); - var prismFolder = System.normalizeSync('prismjs').substring(8); - prismFolder = prismFolder.substring(0, prismFolder.length -3); - var prismFiles = [ - 'prism.js', - 'components/prism-actionscript.js', - 'components/prism-c.js', - 'components/prism-cpp.js', - 'components/prism-csharp.js', - 'components/prism-php.js', - 'components/prism-coffeescript.js', - 'components/prism-go.js', - 'components/prism-haskell.js', - 'components/prism-java.js', - 'components/prism-lua.js', - 'components/prism-matlab.js', - 'components/prism-perl.js', - 'components/prism-python.js', - 'components/prism-r.js', - 'components/prism-ruby.js', - 'components/prism-bash.js', - 'components/prism-swift.js', - 'components/prism-objectivec.js', - 'components/prism-scala.js' - ].map(file => path.join(prismFolder, file)); - - return gulp.src(prismFiles) - .pipe(concat(path.join(paths.tmp, 'prismjs-bundle.js'))) - .pipe(gulp.dest('.')); -}); - -// needs inlineTemplates run before to create .tmp/lib folder -gulp.task('injectVersionFile', function() { - var version = require('../../package.json').version; - var exportStatement = `export var redocVersion = "${version}"`; - fs.writeFileSync(path.join(paths.tmp, 'lib/version.js'), exportStatement); -}) diff --git a/build/tasks/clean.js b/build/tasks/clean.js deleted file mode 100644 index f96f5682..00000000 --- a/build/tasks/clean.js +++ /dev/null @@ -1,9 +0,0 @@ -var gulp = require('gulp'); -var paths = require('../paths'); -var del = require('del'); -var vinylPaths = require('vinyl-paths'); - -gulp.task('clean', function () { - return gulp.src([paths.output, paths.tmp]) - .pipe(vinylPaths(del)); -}); diff --git a/build/tasks/e2e.js b/build/tasks/e2e.js deleted file mode 100644 index ad09d558..00000000 --- a/build/tasks/e2e.js +++ /dev/null @@ -1,34 +0,0 @@ -var gulp = require('gulp'); -var gp = require('gulp-protractor'); -var browserSync = require('browser-sync').create('bs-e2e'); - -gulp.task('test-server', function (done) { - browserSync.init({ - open: false, - notify: false, - port: 3000, - ghostMode: false, - server: { - baseDir: './tests/e2e', - routes: { - '/dist': './dist', - '/swagger.yml': './demo/swagger.yml' - }, - } - }, done); -}); - - -gulp.task('e2e', ['build', 'test-server'], function(done) { - gulp.src(['tests/e2e/**/*.js'], { read:false }) - .pipe(gp.protractor({ - configFile: './protractor.conf.js' - })).on('error', function(e) { - browserSync.exit(); - throw e; - done(); - }).on('end', function() { - browserSync.exit(); - done(); - }); -}); diff --git a/build/tasks/lint.js b/build/tasks/lint.js deleted file mode 100644 index 526dc523..00000000 --- a/build/tasks/lint.js +++ /dev/null @@ -1,15 +0,0 @@ -var gulp = require('gulp'); -var tslint = require('gulp-tslint'); -var paths = require('../paths'); - -gulp.task('lint', function () { - return gulp.src([paths.source, paths.tests]) - .pipe(tslint({ - rulesDirectory: 'node_modules/codelyzer' - })) - .pipe(tslint.report(require('tslint-stylish'), { - emitError: true, - sort: true, - bell: true - })); -}); diff --git a/build/tasks/serve.js b/build/tasks/serve.js deleted file mode 100644 index 93105d83..00000000 --- a/build/tasks/serve.js +++ /dev/null @@ -1,17 +0,0 @@ -var gulp = require('gulp'); -var browserSync = require('browser-sync').create('bs'); - -gulp.task('serve', ['watch'], function (done) { - browserSync.init({ - open: false, - notify: false, - port: 9000, - server: { - baseDir: './demo', - routes: { - '/dist': './dist' - }, - }, - reloadDelay: 500 - }, done); -}); diff --git a/build/tasks/test.js b/build/tasks/test.js deleted file mode 100644 index c572bac5..00000000 --- a/build/tasks/test.js +++ /dev/null @@ -1,52 +0,0 @@ -var gulp = require('gulp'); -var runSequence = require('run-sequence'); -var Server = require('karma').Server; -var remapIstanbul = require('remap-istanbul/lib/gulpRemapIstanbul'); - -gulp.task('prepare-test', function(cb) { - return runSequence( - 'clean', - 'transpile', - 'concatPrism', - 'injectVersionFile', - cb - ); -}) -/** - * Run test once and exit - */ -gulp.task('test', ['prepare-test'], function (done) { - new Server({ - configFile: __dirname + '/../../karma.conf.js', - singleRun: true - }, karmaDone).start(); - - function karmaDone (exitCode) { - remapCoverage(done, exitCode); - } -}); - -function remapCoverage(done, statusCode) { - console.log('Remapping coverage to TypeScript format...'); - gulp.src('coverage/**/coverage-final.json') - .pipe(remapIstanbul({ - reports: { - 'lcovonly': 'coverage/lcov.info', - 'text-summary': 'coverage/text-summary.txt', - 'html': 'coverage' - } - })) - .on('finish', function () { - console.log('Remapping done!'); - console.log(cat('coverage/text-summary.txt').stdout); - console.log('Test Done with exit code: ' + statusCode); - if (process.env.TRAVIS) { - console.log('uploading to coveralls') - var out = cat('coverage/lcov.info').exec('coveralls'); - if (out.code !== 0) { - console.warn('Failed upload to coveralls'); - } - } - done(statusCode); - }); -}; diff --git a/build/tasks/watch.js b/build/tasks/watch.js deleted file mode 100644 index a0cdcdda..00000000 --- a/build/tasks/watch.js +++ /dev/null @@ -1,14 +0,0 @@ -var gulp = require('gulp'); -var paths = require('../paths'); -var browserSync = require('browser-sync').get('bs'); - -function changed(event) { - console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); -} - -gulp.task('watch', ['build'], function () { - gulp.watch([ paths.source ], [ 'build', browserSync.reload ]).on('change', changed); - gulp.watch([ paths.html ], [ 'build', browserSync.reload]).on('change', changed); - gulp.watch([ paths.scss ], [ 'build', browserSync.reload]).on('change', changed); - gulp.watch([ paths.demo ], [ '', browserSync.reload ]).on('change', changed); -}); diff --git a/build/webpack.dev.js b/build/webpack.dev.js new file mode 100644 index 00000000..bdd0deb4 --- /dev/null +++ b/build/webpack.dev.js @@ -0,0 +1,98 @@ +const webpack = require('webpack'); +const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin; + +const root = require('./helpers').root; +const VERSION = JSON.stringify(require('../package.json').version); + +// TODO Refactor common parts of config + +module.exports = { + context: root(), + devtool: 'cheap-module-source-map', + debug: false, + + resolve: { + extensions: ['', '.ts', '.js', '.json', '.css', '.scss', '.html'], + root: root('lib'), + descriptionFiles: ['package.json'], + modules: [ + 'node_modules', + root('lib') + ], + alias: { + './lib/bootstrap': root('lib/bootstrap.dev'), + http: 'stream-http', + https: 'stream-http' + } + }, + externals: { + "jquery": "jQuery" + }, + node: { + fs: "empty" + }, + entry: { + 'redoc': './lib/index.ts', + 'vendor': './lib/vendor.ts', + 'polyfills': './lib/polyfills.ts' + }, + + devServer: { + outputPath: root('dist'), + watchOptions: { + poll: true + }, + port: 9000, + hot: true, + stats: { + modules: false, + cached: false, + chunk: false + } + }, + + output: { + path: root('dist'), + filename: '[name].js', + sourceMapFilename: '[name].map', + chunkFilename: '[id].chunk.js' + }, + + module: { + loaders: [{ + test: /\.ts$/, + loaders: [ + 'awesome-typescript-loader', + 'angular2-template-loader' + ], + exclude: [/\.(spec|e2e)\.ts$/] + },{ + test: /lib\/.*\.css$/, + loaders: ['raw-loader'], + exclude: [/redoc-initial-styles\.css$/] + },{ + test: /\.css$/, + loaders: ['style', 'css?-import'], + exclude: [/lib\/(?!.*redoc-initial-styles).*\.css$/] + },{ + test: /\.html$/, + loader: 'raw-loader' + }] + }, + + plugins: [ + new webpack.HotModuleReplacementPlugin(), + + new webpack.optimize.CommonsChunkPlugin({ + name: ['vendor', 'polyfills'], + minChunks: Infinity + }), + + new webpack.DefinePlugin({ + 'IS_PRODUCTION': false, + 'LIB_VERSION': VERSION + }), + + new ForkCheckerPlugin() + ], +} diff --git a/build/webpack.prod.js b/build/webpack.prod.js new file mode 100644 index 00000000..6943e7d1 --- /dev/null +++ b/build/webpack.prod.js @@ -0,0 +1,82 @@ +const webpack = require('webpack'); + +const VERSION = JSON.stringify(require('../package.json').version); + +const root = require('./helpers').root; +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = { + context: root(), + debug: false, + devtool: 'cheap-module-source-map', + + resolve: { + extensions: ['', '.ts', '.js', '.json', '.css', '.scss', '.html'], + root: root('lib'), + descriptionFiles: ['package.json'], + modules: [ + 'node_modules', + root('lib') + ], + alias: { + http: 'stream-http', + https: 'stream-http' + } + }, + externals: { + "jquery": "jQuery" + }, + node: { + fs: "empty" + }, + entry: { + 'redoc': ['./lib/polyfills.ts', './lib/vendor.ts', './lib/index.ts'] + }, + + output: { + path: root('dist'), + filename: '[name].min.js', + sourceMapFilename: '[name].min.map', + library: 'Redoc', + libraryTarget: 'umd', + umdNamedDefine: true + }, + + module: { + // preLoaders: [{ + // test: /\.js$/, + // loader: 'source-map' + // // }, + loaders: [{ + test: /\.ts$/, + loader: 'awesome-typescript-loader', + exclude: /(node_modules)/ + }, { + test: /\.css$/, + loaders: ['style', 'css?-import'] + }] + }, + + plugins: [ + new webpack.LoaderOptionsPlugin({ + minimize: true, + debug: false + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + screw_ie8: true + }, + mangle: { screw_ie8 : true }, + output: { + comments: false + }, + sourceMap: false + }), + + new webpack.DefinePlugin({ + 'IS_PRODUCTION': true, + 'LIB_VERSION': VERSION + }) + ], +} diff --git a/build/webpack.test.js b/build/webpack.test.js new file mode 100644 index 00000000..9cea6f9e --- /dev/null +++ b/build/webpack.test.js @@ -0,0 +1,100 @@ +const webpack = require('webpack'); +const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin; + +const root = require('./helpers').root; +const VERSION = JSON.stringify(require('../package.json').version); + + +module.exports = { + //context: root(), + devtool: 'inline-source-map', + debug: true, + resolve: { + extensions: ['', '.ts', '.js', '.json', '.css', '.scss', '.html'], + root: root('lib'), + descriptionFiles: ['package.json'], + modules: [ + 'node_modules', + root('lib'), + ], + alias: { + './lib/bootstrap': root('lib/bootstrap.dev'), + http: 'stream-http', + https: 'stream-http' + } + }, + externals: { + "jquery": "jQuery" + }, + node: { + fs: "empty" + }, + + output: { + path: root('dist'), + filename: '[name].js', + sourceMapFilename: '[name].map', + chunkFilename: '[id].chunk.js' + }, + + module: { + loaders: [ { + test: /\.ts$/, + loaders: [ + 'awesome-typescript-loader' + ], + query: { + "sourceMap": false, + "inlineSourceMap": true, + "removeComments": true, + "module": "commonjs" + } + }, { + test: /\.ts$/, + loaders: [ + 'angular2-template-loader' + ], + exclude: [/\.(spec|e2e)\.ts$/] + },{ + test: /lib\/.*\.css$/, + loaders: ['raw-loader'], + exclude: [/redoc-initial-styles\.css$/] + },{ + test: /\.css$/, + loaders: ['style', 'css?-import'], + exclude: [/lib\/(?!.*redoc-initial-styles).*\.css$/] + },{ + test: /\.html$/, + loader: 'raw-loader' + }], + postLoaders: [ + + /** + * Instruments JS files with Istanbul for subsequent code coverage reporting. + * Instrument only testing sources. + * + * See: https://github.com/deepsweet/istanbul-instrumenter-loader + */ + { + test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader', + include: root('lib'), + exclude: [ + /\.(e2e|spec)\.ts$/, + /node_modules/ + ] + } + + ] + }, + + + plugins: [ + + new webpack.DefinePlugin({ + 'IS_PRODUCTION': false, + 'LIB_VERSION': VERSION + }), + + new ForkCheckerPlugin() + ], +} diff --git a/demo/index.html b/demo/index.html index 6e236386..8cb4b1d5 100644 --- a/demo/index.html +++ b/demo/index.html @@ -25,7 +25,10 @@ - + + + + +