使用 Webpack 管理前端 JS


Webpack是啥?官网的解释是“webpack takes modules with dependencies and generates static assets representing those modules.”

说简单点就是一款前端的模块加载兼打包工具。

首先看看项目的目录结构:

- ../resources
  - src
    - imgs               //图片
    - scripts            //JS脚本,按照page、components进行组织
      - page
      - components
    - styles             //CSS脚本,按照page、components进行组织
      - page
      - components
    - templates          //页面ejs
  - static               //webpack编译打包输出目录的静态文件
  - templates            //webpack编译输出的模板静态HTML文件
  - server.js            //用于启动dev模式
  - webpack.config.js    //webpack配置文件

通过 webpack.config.js 来完成各种类型文件的 loader ,让我们一起看看该文件内容:

var path = require('path'),
    glob = require('glob'),
    webpack = require('webpack'),
    WebpackNotifierPlugin = require('webpack-notifier'),
    ExtractTextPlugin = require('extract-text-webpack-plugin'),
    HtmlWebpackPlugin = require('html-webpack-plugin');

var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;

const debug = process.env.NODE_ENV !== 'production'
//const contextPath = process.env.npm_package_config_contextPath;

var entries = getEntry('src/scripts/page/**/*.js', 'src/scripts/page/');
var chunks = Object.keys(entries);
console.log(entries);

var config = {

  entry: entries,
  output: {
    path: path.join(__dirname, 'static'),
    publicPath: 'http://0.0.0.0:9001/static/',
    filename: 'scripts/page/[name]-[hash].js',
    chunkFilename: 'scripts/[id].chunk.js?[hash]'
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract('style', 'css')
      }, {
        test: /\.less$/,
        loader: ExtractTextPlugin.extract('css!less')
      }, {
        test: /\.ejs$/,
        loader: 'ejs-loader'
      }, {
        test: /\.html$/,
        loader: 'html?-minimize' // 避免压缩html,https://github.com/webpack/html-loader/issues/50
      }, {
        test: /\.(woff|woff2|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'file-loader?name=fonts/[name].[ext]'
      }, {
        test: /\.(png|jpe?g|gif)$/,
        loader: 'url-loader?limit=8192&name=imgs/[name]-[hash].[ext]'
      }
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({ // 加载jquery
      $: 'jquery',
      JQuery: 'jquery',
      _: 'underscore'
    }),
    new CommonsChunkPlugin({
      name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
      chunks: chunks,
      minChunks: chunks.length // 提取所有entry共同依赖的模块
    }),
    new WebpackNotifierPlugin({alwaysNotify: true}),
    new ExtractTextPlugin('styles/[name]-[chunkhash].css'), // 单独使用link标签加载css并设置路径,相对于output配置中的publickPath
    debug ? function () {} : new UglifyJsPlugin({ // 压缩代码
      compress: {
        warnings: false
      },
      except: ['$super', '$', 'exports', 'require'] // 排除关键字
    })
  ]

}

var pages = Object.keys(getEntry('src/templates/**/*.ejs', 'src/templates/'));
pages.forEach(function(pathname) {
  var conf = {
    filename: '../templates/' + pathname + '.html', // 生成的html存放路径,相对于path
    template: 'src/templates/' + pathname + '.ejs', // html模板路径
    cache: false,//true | false if true (default) try to emit the file only if it was changed.
    inject: false // js插入的位置,true/'head'/'body'/false
  };
  if (pathname in config.entry) {
    //conf.favicon = path.resolve(__dirname, 'src/imgs/favicon.ico');
    //conf.inject = 'body';
    if (pathname == 'layout') {
      conf.chunks = ['vendors', pathname];
    } else {
      conf.chunks = [pathname];
    }
    conf.hash = false;
  }
  config.plugins.push(new HtmlWebpackPlugin(conf));
});

module.exports = config

function getEntry(globPath, pathDir) {

  var files = glob.sync(globPath);
  var entries = {}, entry, dirname, basename, pathname, extname;

  for (var i = 0; i < files.length; i++) {
    entry = files[i];
    dirname = path.dirname(entry);
    extname = path.extname(entry);
    basename = path.basename(entry, extname);
    pathname = path.normalize(path.join(dirname, basename));

    pathDir = path.normalize(pathDir);
    if (pathname.startsWith(pathDir)) {
      pathname = pathname.substring(pathDir.length);
    }
    entries[pathname] = ['./' + entry];
  }

  return entries;

}

当我们开发的时候,只需要在 src/scripts/page 下编写 js 文件,src/templates 下编辑对于文件名的.ejs 文件。

举例说明
  • JS 开发案例 index.js
require('../../styles/page/index.css')

var message = 'Hello World';
console.log(message);
  • HTML 案例 index.ejs
<!DOCTYPE HTML>
<html layout:decorate="~{layout.html}">

  <head>
    <title>首页</title>
    <!-- 引入css文件 -->
    <% for (key in htmlWebpackPlugin.files.css) { %>
    <link href="<%= htmlWebpackPlugin.files.css[key] %>" rel="stylesheet">
    <% } %>
  </head>

  <body>
  <div layout:fragment="content">
    <h2>Hello World.</h2>
    <div class="img">
      <!-- 引入图片 -->
      <img src="<%= require('../imgs/test.png') %>" alt="">
    </div>
  </div><!-- content -->
  </body>

  <th:block layout:fragment="scripts">
    <!-- 引入js文件 -->
    <% for (key in htmlWebpackPlugin.files.chunks) { %>
    <script src="<%= htmlWebpackPlugin.files.chunks[key].entry %>" type="text/javascript"></script>
    <% } %>
    <script type="text/javascript" th:inline="javascript">
    </script>
  </th:block>
</html>
参考文献