chrisvfritz / prerender-spa-plugin

Prerenders static HTML in a single-page application.
MIT License
7.32k stars 634 forks source link

Uncaught SyntaxError: Unexpected token < #208

Closed Ocelyn closed 6 years ago

Ocelyn commented 6 years ago

Thank you for your new version, it's really an amazing work !

When I build my Vue Application with webpack I have the following error : Uncaught SyntaxError: Unexpected token <

For each file I want to load, for exemple, if I click on the js file, it will open my index.html file. All my project is located in a subdirectory folder : /en

Here are my webpack configuration

const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSpaPlugin.PuppeteerRenderer
const OfflinePlugin = require('offline-plugin');

const env = process.env.NODE_ENV === 'testing'
  ? require('../config/test.env')
  : config.build.env

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  devtool: 'cheap-module-source-map',
  output: {
    path: path.join(__dirname, '../dist/en/'),
    publicPath: '/en/',
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.ModuleConcatenationPlugin(),

    // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      compress: {
        warnings: false,
        screw_ie8: true,
        drop_console: true
      },
      output: {
        comments: false,
      },
      sourceMap: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css')
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true,
        autoprefixer: {
          add: true,
          browsers: [
            'last 4 versions',
            'android 4',
            'opera 12',
            'ie 11'
          ]
        }
      }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),

    // keep module.id stable when vender modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ]),

    //Offline support PWA
    new OfflinePlugin({
      AppCache: false,
      ServiceWorker: { events: true },
    }),

    new PrerenderSpaPlugin({
      staticDir: __dirname,
      outputDir: path.join(__dirname, '../dist'),
      indexPath: path.join(__dirname, '../dist/en', 'index.html'),
      routes: [
        '/en/',
        '/en/about',
        '/en/works/kyoro',
        '/en/works/sharp'
      ],
      postProcess (renderedRoute) {
        // Ignore any redirects.
        renderedRoute.path = renderedRoute.originalPath
        // Basic whitespace removal. (Don't use this in production.)
        //renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
        // Remove /index.html from the output path if the dir name ends with a .html file extension.
        // For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
        if (renderedRoute.path.endsWith('.html')) {
          renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.path)
        }

        var titles = {
          '/en/': 'ZIZO - Digital and Creative Company in Japan',
          '/en/about': 'About Us - ZIZO - Digital and Creative Company in Japan',
          '/en/works/kyoro': 'Kyoro-chan Commemorative Site - ZIZO - Digital and Creative Company in Japan',
          '/en/works/omron': 'OMRON - ZIZO - Digital and Creative Company in Japan',
          '/en/works/sharp': 'SHARP Emopa Life Story Web - ZIZO - Digital and Creative Company in Japan'
        }

        var desc = {
          '/en/': 'ZIZO Co., Ltd. is a Japanese full service digital agency specializing in Branded Content Marketing, Web Design, SEO, Social Media promotion and Advertising.',
          '/en/about': 'Since our start in 2010, we lead brands and products to success with creative planning, digital strategy and technology. We have a talented team of 30 people formed by experts in different areas to provide high performance and best expertise.',
          '/en/works/kyoro': 'Kyoro-chan” is a cute bear shaped ice shaver first released back in the 1970s. This product from TIGER CORPORATION was a real hit in Japan since as you rotate the handle, the eyes of Kyoro-chan will move. We planned and created this commemorative comeback website to convey the tenderness of this iconic product.',
          '/en/works/omron': 'Kazoku Mesen (Family Eye) is a smart home monitor that sends a notice to your smartphone when your baby needs. With its different uses in an app, it can also monitor your pets or even be your security camera in the front door. It’s able to recognize faces and send messages, becoming the most reliable device for your family.',
          '/en/works/sharp': 'We built the promotional campaign site for the “Emopa Movie”,a video compilation with famous Japanese personalities to show the best features of Emopa, the smartphone AI system that speaks like a real friend. Our mission was to build the perfect showcase for the promotion to be enjoyed by even more people.'
        }

        var ogp = {
          '/en/': 'ogp_index.jpg',
          '/en/about': 'ogp_about.png',
          '/en/works/kyoro': 'ogp_index.jpg',
          '/en/works/omron': 'ogp_index.jpg',
          '/en/works/sharp': 'ogp_index.jpg'
        }

        var _url = {
          '/en/': '/',
          '/en/about': '/about',
          '/en/works/kyoro': '/works/kyoro',
          '/en/works/omron': '/works/omron',
          '/en/works/sharp': '/works/sharp'
        }

        return renderedRoute.html.replace(
          /<title>[^<]*<\/title>/i,
          '<title>' + titles[context.route] + '</title><meta name="description" content="'+desc[context.route]+'"><meta property="og:title" content="'+titles[context.route]+'" /><meta name="twitter:title" content="'+titles[context.route]+'" /><meta property="og:description" content="'+desc[context.route]+'" /><meta name="twitter:description" content="'+desc[context.route]+'" /><meta property="og:image" content="https://zizo.ne.jp/static/share/'+ogp[context.route]+'" /><meta property="twitter:image" content="https://zizo.ne.jp/static/share/' + ogp[context.route] + '" /><meta property="og:url" content="https://zizo.ne.jp/en' + _url[context.route] + '"><meta name="twitter:url" content="https://zizo.ne.jp/en'+_url[context.route]+'" />'
        )
      },
      minify: {
        collapseBooleanAttributes: true,
        collapseWhitespace: true,
        decodeEntities: true,
        keepClosingSlash: true,
        sortAttributes: true
      },
      renderer: new Renderer({
        maxConcurrentRoutes: 5,
        renderAfterTime: 25000,
        devtools: true,
        headless: false
      })
    })
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

Do you have any idea on what to do?

Thank you so much for your help !

JoshTheDerf commented 6 years ago

Hi @Ocelyn!

It sounds like your static files are located somewhere other than where prerender-spa-plugin thinks they are. Try setting staticDir: path.join(__dirname, '../dist/en') in the new PrerenderSPAPlugin({...}) section of the config.

Hope that helps!

Ocelyn commented 6 years ago

Hello @Tribex Thank you for your help, I tried almost all the different paths possible including this one and it didn't change anything. I keep searching !

Ocelyn commented 6 years ago

OK found it !

Here is the setup for people who might have the same problem

const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSpaPlugin.PuppeteerRenderer
const OfflinePlugin = require('offline-plugin');

const env = process.env.NODE_ENV === 'testing'
  ? require('../config/test.env')
  : config.build.env

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  devtool: 'cheap-module-source-map',
  output: {
    path: path.join(__dirname, '../dist/en/'),
    publicPath: '/en/',
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.ModuleConcatenationPlugin(),

    // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      compress: {
        warnings: false,
        screw_ie8: true,
        drop_console: true
      },
      output: {
        comments: false,
      },
      sourceMap: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css')
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true,
        autoprefixer: {
          add: true,
          browsers: [
            'last 4 versions',
            'android 4',
            'opera 12',
            'ie 11'
          ]
        }
      }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),

    // keep module.id stable when vender modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ]),

    //Offline support PWA
    new OfflinePlugin({
      AppCache: false,
      ServiceWorker: { events: true },
    }),

    new PrerenderSpaPlugin({
      staticDir: path.join(__dirname, '../dist'),
      outputDir: path.join(__dirname, '../dist'),
      indexPath: path.join(__dirname, '../dist/en', 'index.html'),
      routes: [
        '/en/',
        '/en/about',
        '/en/works/kyoro',
        '/en/works/sharp'
      ],
      postProcess (context) {
        // Ignore any redirects.
        context.path = context.originalPath
        // Basic whitespace removal. (Don't use this in production.)
        context.html = context.html.split(/>[\s]+</gmi).join('><')
        // Remove /index.html from the output path if the dir name ends with a .html file extension.
        // For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
        //if (context.path.endsWith('.html')) {
          //context.outputPath = path.join(__dirname, 'dist', context.path)
        //}

        var titles = {
          '/en/': 'ZIZO - Digital and Creative Company in Japan',
          '/en/about': 'About Us - ZIZO - Digital and Creative Company in Japan',
          '/en/works/kyoro': 'Kyoro-chan Commemorative Site - ZIZO - Digital and Creative Company in Japan',
          '/en/works/omron': 'OMRON - ZIZO - Digital and Creative Company in Japan',
          '/en/works/sharp': 'SHARP Emopa Life Story Web - ZIZO - Digital and Creative Company in Japan'
        }

        var desc = {
          '/en/': 'ZIZO Co., Ltd. is a Japanese full service digital agency specializing in Branded Content Marketing, Web Design, SEO, Social Media promotion and Advertising.',
          '/en/about': 'Since our start in 2010, we lead brands and products to success with creative planning, digital strategy and technology. We have a talented team of 30 people formed by experts in different areas to provide high performance and best expertise.',
          '/en/works/kyoro': 'Kyoro-chan” is a cute bear shaped ice shaver first released back in the 1970s. This product from TIGER CORPORATION was a real hit in Japan since as you rotate the handle, the eyes of Kyoro-chan will move. We planned and created this commemorative comeback website to convey the tenderness of this iconic product.',
          '/en/works/omron': 'Kazoku Mesen (Family Eye) is a smart home monitor that sends a notice to your smartphone when your baby needs. With its different uses in an app, it can also monitor your pets or even be your security camera in the front door. It’s able to recognize faces and send messages, becoming the most reliable device for your family.',
          '/en/works/sharp': 'We built the promotional campaign site for the “Emopa Movie”,a video compilation with famous Japanese personalities to show the best features of Emopa, the smartphone AI system that speaks like a real friend. Our mission was to build the perfect showcase for the promotion to be enjoyed by even more people.'
        }

        var ogp = {
          '/en/': 'ogp_index.jpg',
          '/en/about': 'ogp_about.png',
          '/en/works/kyoro': 'ogp_index.jpg',
          '/en/works/omron': 'ogp_index.jpg',
          '/en/works/sharp': 'ogp_index.jpg'
        }

        var _url = {
          '/en/': '/',
          '/en/about': '/about',
          '/en/works/kyoro': '/works/kyoro',
          '/en/works/omron': '/works/omron',
          '/en/works/sharp': '/works/sharp'
        }

        context.html = context.html.replace(
          /<title>[^<]*<\/title>/i,
          '<title>' + titles[context.route] + '</title><meta name="description" content="'+desc[context.route]+'"><meta property="og:title" content="'+titles[context.route]+'" /><meta name="twitter:title" content="'+titles[context.route]+'" /><meta property="og:description" content="'+desc[context.route]+'" /><meta name="twitter:description" content="'+desc[context.route]+'" /><meta property="og:image" content="https://zizo.ne.jp/static/share/'+ogp[context.route]+'" /><meta property="twitter:image" content="https://zizo.ne.jp/static/share/' + ogp[context.route] + '" /><meta property="og:url" content="https://zizo.ne.jp/en' + _url[context.route] + '"><meta name="twitter:url" content="https://zizo.ne.jp/en'+_url[context.route]+'" />'
        )

        return context
      },
      minify: {
        collapseBooleanAttributes: true,
        collapseWhitespace: true,
        decodeEntities: true,
        keepClosingSlash: true,
        sortAttributes: true
      },
      renderer: new Renderer({
        maxConcurrentRoutes: 5,
        renderAfterTime: 25000,
        devtools: true,
        headless: false
      })
    })
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig
JoshTheDerf commented 6 years ago

Good for you! Glad you figured it out!

cyanbluefire commented 5 years ago

How do you solve this problem?I need your help.

viaonweb commented 5 years ago

'publicPath' no support non-root directories