{"id":13672068,"url":"https://github.com/vhtml/webpack-MultiplePage","last_synced_at":"2025-04-27T21:31:54.225Z","repository":{"id":85246291,"uuid":"52521076","full_name":"vhtml/webpack-MultiplePage","owner":"vhtml","description":"基于webpack的前端工程化方案（自动入口配置及后端模板）","archived":false,"fork":false,"pushed_at":"2017-05-16T12:54:55.000Z","size":629,"stargazers_count":303,"open_issues_count":7,"forks_count":56,"subscribers_count":19,"default_branch":"master","last_synced_at":"2024-08-03T09:11:26.364Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vhtml.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-02-25T11:57:30.000Z","updated_at":"2023-10-25T07:25:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"efe20687-7fd4-44c2-8838-cb7617dbf831","html_url":"https://github.com/vhtml/webpack-MultiplePage","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vhtml%2Fwebpack-MultiplePage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vhtml%2Fwebpack-MultiplePage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vhtml%2Fwebpack-MultiplePage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vhtml%2Fwebpack-MultiplePage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vhtml","download_url":"https://codeload.github.com/vhtml/webpack-MultiplePage/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224087120,"owners_count":17253507,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T09:01:25.764Z","updated_at":"2024-11-11T10:30:17.710Z","avatar_url":"https://github.com/vhtml.png","language":"JavaScript","readme":"## 基于webpack的前端工程化开发之多页站点篇（二）\n\n**声明：此文只作为webpack入门学习交流，不作为实际项目参考（基于webpack1.x）。**\n\n这篇，我们要解决上篇留下的两个问题：\n\n- webpack如何自动发现entry文件及进行相应的模板配置\n- 如何直接处理后端模板的样式、脚本自动引入问题\n\n以express项目为例，使用express-generator构建一个初始项目，然后再添加需要的目录，最终的目录架构如下：\n\n```\n- website\n  - bin             #express项目启动文件\n  - lib             #express项目开发所需的库\n  + routes          #express项目路由\n    - src           #前端源码开发目录\n      - styles      #css目录，按照页面（模块）、通用、第三方三个级别进行组织\n        + page\n        + common\n        + lib\n      + imgs        #图片资源\n      - scripts     #JS脚本，按照page、components进行组织\n        + page\n        + components\n      + views       #HTML模板\n    - public        #webpack编译打包输出目录的静态文件，express工程的静态目录，可由webpack打包自动生成\n      + styles                \n      + scripts\n      + imgs\n    + views              #webpack编译输出的模板静态文件，express工程的视图模板，可由webpack打包自动生成\n    + node_modules       #所使用的nodejs模块\n    package.json         #项目配置\n    webpack.config.js    #webpack配置\n    README.md            #项目说明\n```\n\u003e 你同样可以根据个人喜好自由设计目录结构。完整的源码示例前往\u003chttps://github.com/vhtml/webpack-MultiplePage\u003e。\n\npackage.json里最终的声明依赖如下：\n\n```javascript\n\"devDependencies\": {\n  \"css-loader\": \"^0.23.1\",\n  \"extract-text-webpack-plugin\": \"^1.0.1\",\n  \"file-loader\": \"^0.8.5\",\n  \"glob\": \"^7.0.0\",\n  \"html-loader\": \"^0.4.3\",\n  \"html-webpack-plugin\": \"^2.9.0\",\n  \"jquery\": \"^1.12.0\",\n  \"less\": \"^2.6.0\",\n  \"less-loader\": \"^2.2.2\",\n  \"style-loader\": \"^0.13.0\",\n  \"url-loader\": \"^0.5.7\",\n  \"webpack\": \"^1.12.13\",\n  \"webpack-dev-server\": \"^1.14.1\"\n}\n```\n可以看出，比上篇多了一个glob依赖，它是一个根据模式匹配获取文件列表的node模块。有关glob的详细用法可以在这里看到——\u003chttps://github.com/isaacs/node-glob\u003e。利用glob模块可以很方便的获取src/scripts/page路径下的所有js入口文件。同理，可以实现自动的进行与入口文件相对应的模板配置。\n\n最终的webpack配置如下（一些注释可能会让你少走许多坑）：\n\n```javascript\nvar path = require('path');\nvar glob = require('glob');\nvar webpack = require('webpack');\nvar ExtractTextPlugin = require('extract-text-webpack-plugin');\nvar HtmlWebpackPlugin = require('html-webpack-plugin');\nvar CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;\nvar UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;\n\nconst debug = process.env.NODE_ENV !== 'production';\n\nvar entries = getEntry('src/scripts/page/**/*.js', 'src/scripts/page/');\nvar chunks = Object.keys(entries);\nvar config = {\n  entry: entries,\n  output: {\n    path: path.join(__dirname, 'public'),\n    publicPath: '/static/',\n    filename: 'scripts/[name].js',\n    chunkFilename: 'scripts/[id].chunk.js?[chunkhash]'\n  },\n  module: {\n    loaders: [ //加载器\n      {\n        test: /\\.css$/,\n        loader: ExtractTextPlugin.extract('style', 'css')\n      }, {\n        test: /\\.less$/,\n        loader: ExtractTextPlugin.extract('css!less')\n      }, {\n        test: /\\.html$/,\n        loader: \"html?-minimize\"  //避免压缩html,https://github.com/webpack/html-loader/issues/50\n      }, {\n        test: /\\.(woff|woff2|ttf|eot|svg)(\\?v=[0-9]\\.[0-9]\\.[0-9])?$/,\n        loader: 'file-loader?name=fonts/[name].[ext]'\n      }, {\n        test: /\\.(png|jpe?g|gif)$/,\n        loader: 'url-loader?limit=8192\u0026name=imgs/[name]-[hash].[ext]'\n      }\n    ]\n  },\n  plugins: [\n    new webpack.ProvidePlugin({ //加载jq\n      $: 'jquery'\n    }),\n    new CommonsChunkPlugin({\n      name: 'vendors', // 将公共模块提取，生成名为`vendors`的chunk\n      chunks: chunks,\n      minChunks: chunks.length // 提取所有entry共同依赖的模块\n    }),\n    new ExtractTextPlugin('styles/[name].css'), //单独使用link标签加载css并设置路径，相对于output配置中的publickPath\n    debug ? function() {} : new UglifyJsPlugin({ //压缩代码\n      compress: {\n        warnings: false\n      },\n      except: ['$super', '$', 'exports', 'require'] //排除关键字\n    }),\n  ]\n};\n\n\nvar pages = Object.keys(getEntry('src/views/**/*.html', 'src/views/'));\npages.forEach(function(pathname) {\n  var conf = {\n    filename: '../views/' + pathname + '.html', //生成的html存放路径，相对于path\n    template: 'src/views/' + pathname + '.html', //html模板路径\n    inject: false,  //js插入的位置，true/'head'/'body'/false\n    /*\n    * 压缩这块，调用了html-minify，会导致压缩时候的很多html语法检查问题，\n    * 如在html标签属性上使用{{...}}表达式，所以很多情况下并不需要在此配置压缩项，\n    * 另外，UglifyJsPlugin会在压缩代码的时候连同html一起压缩。\n    * 为避免压缩html，需要在html-loader上配置'html?-minimize'，见loaders中html-loader的配置。\n     */\n    // minify: { //压缩HTML文件\n    //  removeComments: true, //移除HTML中的注释\n    //  collapseWhitespace: false //删除空白符与换行符\n    // }\n  };\n  if (pathname in config.entry) {\n    conf.favicon = 'src/imgs/favicon.ico';\n    conf.inject = 'body';\n    conf.chunks = ['vendors', pathname];\n    conf.hash = true;\n  }\n  config.plugins.push(new HtmlWebpackPlugin(conf));\n});\n\n\nmodule.exports = config;\n\nfunction getEntry(globPath, pathDir) {\n  var files = glob.sync(globPath);\n  var entries = {},\n    entry, dirname, basename, pathname, extname;\n\n  for (var i = 0; i \u003c files.length; i++) {\n    entry = files[i];\n    dirname = path.dirname(entry);\n    extname = path.extname(entry);\n    basename = path.basename(entry, extname);\n    pathname = path.join(dirname, basename);\n    pathname = pathDir ? pathname.replace(new RegExp('^' + pathDir), '') : pathname;\n    entries[pathname] = ['./' + entry];\n  }\n  return entries;\n}\n```\n\n建立一个开发环境服务器启动脚本server.js:\n\n```javascript\nvar fs = require('fs');\nvar webpack = require('webpack');\nvar WebpackDevServer = require('webpack-dev-server');\nvar config = require('./webpack.config');\n\nvar serverPort = 54999,\n  devPort = 8082;\n\nvar exec = require('child_process').exec;\nvar cmdStr = 'PORT=' + serverPort + ' supervisor ./bin/www';\nexec(cmdStr);\n\n\nfor (var i in config.entry) {\n  config.entry[i].unshift('webpack-dev-server/client?http://localhost:' + devPort, \"webpack/hot/dev-server\")\n}\nconfig.plugins.push(new webpack.HotModuleReplacementPlugin());\n\n\nvar proxy = {\n  \"*\": \"http://localhost:\" + serverPort\n};\n//启动服务\nvar app = new WebpackDevServer(webpack(config), {\n  publicPath: '/static/',\n  hot: true,\n  proxy: proxy\n});\napp.listen(devPort, function() {\n  console.log('dev server on http://0.0.0.0:' + devPort+'\\n');\n});\n```\n\n然后，只需要在项目目录下执行`npm run dev`就可以开始进行开发了。\n\n有些同学会发现一个问题，热加载经常无法生效，这个是由于热加载只能针对有`module.exports`输出的模块才行，否则会导致热加载失败从而刷新浏览器，而对于入口js文件由于没有模块输出，就会发现总是刷新浏览器了。如果要禁止自动刷新浏览器，可以将server.js中的`\"webpack/hot/dev-server\"`改为`\"webpack/hot/only-dev-server\"`。\n\n\u003cdel\u003e还有一个蛋疼的问题就是，webpack-dev-server监控文件变化生成的内容是放在内存里的，由于没有输出到打包目录下，则`/views`目录下的文件没有变化，supervisor之类的工具检测不到变化，从而不会刷新视图。只好在改动模板文件后，执行`webpack`命令打包一下。于是比较蛋疼的在server.js的最后加上了这段代码：\u003c/del\u003e\n\n```javascript\nfs.watch('./src/views/', function() {\n  exec('webpack --progress --hide-modules', function(err, stdout, stderr) {\n    if (err) {\n      console.log(stderr);\n    } else {\n      console.log(stdout);\n    }\n  });\n});\n```\n\u003cdel\u003e在检测到有模板改动的时候会自动重新打包，然后只需手动刷新下浏览器即可。__显然这样做是比较低效的__。\u003c/del\u003e可以看下[这里](https://github.com/vhtml/webpack-MultiplePage/issues/7)。（备注：已更新到新的提交上）\n\n这里还要说说如何直接处理后端模板的问题。一开始本菜也是对这个问题进行了苦苦的探索，觉得可能真的实现不了，一度要放弃，并打算采用先纯静态打包再改写成后端模板的方式（因为貌似还没有这样的loader可以很智能的处理模板include的问题以及在非html模板中自动引入css和js）。但是这样做真的很蛋疼啊有木有！明明是一件事为什么要拆成两件事去做呢？！\n\n如果你也进行过这样一番探索，你可能接触过像jade-loader、ejs-loader、ejs-compiled-loader等这样的webpack loader。无奈它们统统都不是我要找的，它们只是编译了模板而没有保留模板原有的生态，也不能自动地引入css和js。我也曾试过自己写loader将ejs模板先转成html模板（只处理include标签，其余原样保留）再用html-loader去处理，但又破坏了模板的可复用性，失去了灵活性。\n\n好吧，其实只是想原样输出src/views中的模板，然后像上篇中那样自动引入css和js，仅此而已。没想到差一点钻了死胡同，想得过于复杂了。\n\n我们应该先知道一个事实，html-webpack-plugin插件实现自动引入css和js的原理，是在模板中对应的成对head和body标签中进行解析插入。如果没有head和body标签，它会分别在模板头和尾生成这两个标签并插入link和script标签来引入css和js。而至于你的模板里写了什么，它是不会关心的。明白了这个原理，要完成“大业”就为期不远了。我们应该先改一改写模板的方式，模板结构一定要是类html的，不能是jade这种（还好我并不喜欢用jade）。以artTemplate模板为例，如下：\n\n```xml\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  {{include './common/meta'}}\n\u003c/head\u003e\n\u003cbody\u003e\n  {{include './common/header'}}\n  \u003cdiv class=\"g-bd\"\u003e\n    {{include './common/_content'}}\n  \u003c/div\u003e\n  {{include './common/footer'}}\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n是的，没错，只要保留完整的head、body结构即可。然后根据上述的webpack配置，将与入口js对应的模板插入link和script标签并输出到./views目录中，其余模板原样输出到./views目录或相应的子目录下即可。\n\n到此，“大业”完成。\n\n假如你有更好的解决方案，欢迎一起分享。\n\n### 快速开始\n\n```bash\ngit clone https://github.com/vhtml/webpack-MultiplePage.git  #克隆最新项目到本地\ncd webpack-MultiplePage  #切换到项目路径下\nnpm install #安装依赖\nnode server #执行开发环境脚本，因为server.js中使用supervisor启动node程序，你可能需要全局安装一下supervisor\n```\n\n在浏览器中打开http://localhost:8082/。\n\n如果需要上线，执行`npm run build`完成最终项目打包即可。\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvhtml%2Fwebpack-MultiplePage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvhtml%2Fwebpack-MultiplePage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvhtml%2Fwebpack-MultiplePage/lists"}