webpack 多个页面-webpack打包多个页面的方式

2023-08-21 0 9,652 百度已收录

前言

第一次接触webpack是因为使用了Vue,因为Vue的脚手架就是使用webpack搭建的。 一开始我以为webpack就是为了打包单页而生的,但后来想想,这么好的打包方案只用在单页上是不是很浪费资源呢? 如果webpack能用在传统的多页面上,一开始会不会效率更高? 幸运的是,很多优秀的后端开发人员已经写了很多demo和文章供人们学习。 我也写了一个小项目,希望对大家学习webpack有帮助。

好吧,虽然我上面说的都是废话,但是接下来我会附上项目地址和干货,这样更好吃。

webpack 多页面

项目解决的问题

src目录对应dist目录

当我们使用webpack打包多个页面时,我们希望src目录对应打包后的dist目录,如上图所示。 开发框架按照页面模块的思想构建,然后通过webpack打包生成传统页面的结构。

/**
 * 创建打包路径
 */
const createFiles = function() {
 const usePug = require('../config').usePug;
 const useTypeScript = require('../config').useTypeScript;
 const path = require('path');
 const glob = require('glob');
 const result = [];
 const type = usePug ? 'pug' : 'html';
 const scriptType = useTypeScript ? 'ts' : 'js';
 const files = glob.sync(path.join(__dirname, `../src/views/**/*.${type}`));
 for (file of files) {
  result.push({
   name: usePug ? file.match(/w{0,}(?=.pug)/)[0] : file.match(/w{0,}(?=.html)/)[0],
   templatePath: file,
   jsPath: file.replace(type, scriptType),
   stylePath: file.replace(type, 'stylus')
  });
 }
 return result;
};

使用该方法,我们可以获取待打包的文件路径(方法中获取文件路径的模块也可以使用fs模块),根据打包的文件路径,我们可以使用html-webpack-plugin来实现多页包装。

由于 html-webpack-plugin 的每个对象实例仅针对/生成一个页面,因此如果我们制作多页面应用程序,我们需要配置多个 html-webpack-plugin 对象实例:

const plugins = function() {
 const files = createFiles();
 const HtmlWebpackPlugin = require('html-webpack-plugin');
 const path = require('path');
 const ExtractTextPlugin = require('extract-text-webpack-plugin');
 let htmlPlugins = [];
 let Entries = {};
 files.map(file => {
  htmlPlugins.push(
   new HtmlWebpackPlugin({
    filename: `${file.name}.html`,
    template: file.templatePath,
    chunks: [file.name]
   })
  );
  Entries[file.name] = file.jsPath;
 });
 return {
  plugins: [
   ...htmlPlugins,
   new ExtractTextPlugin({
    filename: getPath => {
     return getPath('css/[name].css');
    }
   })
  ],
  Entries
 };
};

由于我使用了ExtractTextPlugin,这段CSS代码最终会以CSS文件的形式生成到它所属的chunk的目录中。

模板引擎

为每个页面维护相同的 UI 布局是非常困难的。 如果UI稍微改一下,就得去每个页面去改。 非常麻烦而且容易错过。 怎么能破呢?

考虑到这个问题,项目引入并使用了pug模板引擎。

现在,我们可以使用 pug 的功能来创建共享组件:

演示.pug

p 这是一个共享组件

然后,当你需要使用这个公共组件时,你可以导入它:

include 'demo.pug'

此外,您还可以使用哈巴狗的所有特殊功能。

在webpack中配置pug也很简单,首先安装:

npm i --save-dev pug pug-html-loader

然后将所有.html后缀改为.pug后缀,并使用pug句型。

然后减少规则中的另一个配置

{
  test: /.pug$/,
  use: 'pug-html-loader'
}

同时,plugins对象中index.html使用的HtmlWebpackPlugin中的模板也应该改为index.pug。

Webpack 集成 eslint

先放出配置代码:

if (useEslint) {
 loaders.push({
  test: /.js$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  include: [path.resolve(__dirname, 'src')],
  options: {
   formatter: require('stylish')
  }
 });
}

通过将ESLint与webpack集成,我们可以保证编译生成的代码没有语句错误,符合编码标准; 但在开发过程中,可能要等到编译的时候才意识到问题。

因此,我建议可以将ESLint集成到编辑器或IDE中。 比如我自己使用vs code的时候,可以使用一个叫做Eslint的插件。 一旦我写出有问题的代码,它就会立即被标记。

开发环境和生产环境

首先,在阅读webpacl项目时webpack 多个页面,一般都会先看package.json文件。因为当你在命令行输入下一条命令时

npm run dev

webpack会找到package.json文件中的script属性,依次分析命令。 可以看到这条命令会被相应执行

复制代码代码如下:

nodemon --watch build/ --exec "cross-env NODE_ENV=development webpack-dev-server --color --progress --config build/webpack.dev.js"

同样,编写命令时

npm run build

脚本将被执行

ross-env NODE_ENV=production webpack --config build/webpack.prod.js

这样可以区分开发环境和生产环境。

虽然我们会对环境进行区分webpack 多个页面,但基于不重复的原则,将两个环境的项目的通用配置集成到(build/webpack.base.js)文件中。然后将配置与webpack-merge 插件的帮助

在 webpack 中使用 jquery

在webpack中使用jquery也很简单,我们可以减少loader中的一个配置:

if (useJquery) {
 loaders.push({
  // 通过require('jquery')来引入
  test: require.resolve('jquery'),
  use: [
   {
    loader: 'expose-loader',
    // 暴露出去的全局变量的名称 随便你自定义
    options: 'jQuery'
   },
   {
    // 同上
    loader: 'expose-loader',
    options: '$'
   }
  ]
 });
}

那么当你需要在js文件中使用jq时,只需引用暴露的变量名即可:

import $ from 'jQuery';

在 webpack 中使用 typescript

在webpack中使用jquery也很简单,我们可以减少loader中的一个配置:

if (useTs) {
 loaders.push({
  test: /.tsx?$/,
  use: 'ts-loader',
  exclude: /node_modules/
 });
}

然后将js文件改为ts.

以上就是本文的全部内容。 希望对您的学习有所帮助,也希望您多多支持脚本之家。

carletsky.github.io/2019/02/19/webpack-bundling-libraries-with-dynamic-imports/

在编译库时,我们有时希望按需加载个别依赖项,例如,如果代码的运行环境不支持个别功能,则加载相关的Polyfill。

webpack作为目前最流行的打包工具,已经支持动态加载的功能。

本文将讨论一种用 webpack 打包动态加载解释器的方法。

请注意,本文专门针对一般作者。 如果读者正在编写应用程序,则无需往下读。

泛型示例

// my-lib.jsclass MyLib {
   loadDeps
() {
       
return new Promise((resolve, reject) => {
           
if (global.TextDecoder === undefined) {
               
return Promise.all([
                   
import('./deps/text-encoding'),
                   
import('./deps/other-dep.js'),
                   
import('./deps/another-dep.js'),
               
]).then(resolve).catch(reject);
           
} else {
               resolve
();
           
}
       
});
   
}

   initialize
() {
       
this.decoder = new TextDecoder();
       
// ...
   
}}

该例程中有一个loadDeps方法,该方法会根据运行环境监控TextDecoder是否存在。 如果不存在,则会加载本地目录中的依赖项。

当我们将解释器发布到 npm 时,我们将像这样使用它:

// app.jsimport MyLib from 'my-lib';class App {
   constructor
() {
       
this.myLib = new MyLib();
   
}}const app = new App();
app
.myLib.loadDeps().then(() => {
   app
.myLib.initialize();
   console
.log(app.myLib.decoder);});

这样我们的解释器无论在什么环境下都可以使用TextDecoder。

可是,事情真的会这么顺利吗?

问题

在发布到npm仓库之前,我们首先使用Webpack来构建项目。 这时,webpack会分析文件中的依赖路径,并将依赖文件生成到output.path中。 同时,import()方法已经被做成了webpack内部的方式,依赖路径也与output.publicPath相关。

假设我们的webpack配置是这样的:

// webpack.config.jsmodule.exports = {
   output
: {
       filename
: 'index.js',
       chunkFilename
: '[name].js',
       path
: 'dist/',
       publicPath
: '/',
       libraryTarget
: 'umd'
   
},
   
// ...};

之后输出这些文件:

// webpack outputBuilt at: 02/19/2019 5:08:41 PM
 
Asset      Size  Chunks             Chunk Names
 
1.js  79 bytes       1  [emitted]
 
2.js  79 bytes       2  [emitted]
 
3.js  79 bytes       3  [emitted]
index
.js   144 KiB       0  [emitted]  main
Entrypoint main = index.js

那么当应用层调用app.myLib.loadDeps()时,加载依赖的路径会发生什么变化呢?

你猜对了,浏览器会尝试加载这个路径:

/1.js/2.js/3.js

但结果呢? 其实是404。因为网站根目录下没有这个文件,除非你把那些文件复制到网站根目录下。

同理,如果publicPath是相对路径(假设为''),那么请求的路径将是相对于当前URL的,即如果当前URL是/topics/:uid,那么请求的路径将是 /topics/ :uid/1.js。 除非当前目录存在这样的依赖,否则结果仍然是404。

事实上,我们还可以更改服务器配置,将 /1.js 和 **/1.js 都指向 /path/to/project/node_modules/my-lib/dist/1.js。

对于应用层来说webpack按需加载,改变一个库的服务器配置就变得很麻烦。

对于泛型来说,应该管理自己的依赖关系,不应该让应用层甚至服务器端配合。

解决方案

我们需要解决这个路径问题,既能保证结果正确,又能方便开发。

显然,这个问题是webpack打包时对import()的处理导致的。 如果我们不在编译时处理,而是在运行时处理,这不就达到目的了吗?

我们先规划一下dist/的目录结构,并更改webpack的配置:

const CopyWebpackPlugin = require('copy-webpack-plugin');module.exports = {
   output
: { ... },
   plugins
: [
     
new CopyWebpackPlugin([{
         
from: 'src/deps/',
          to
: '.'
     
}])
   
]}

CopyWebpackPlugin会将src/deps/中的文件复制到dist/目录下,然后dist/就会变成:

dist
├── 1.js├── 2.js├── 3.js├── text-encoding.js
├── other-dep.js
├── another-dep.js
└── index.js

下一步是让webpack不解析import(),这里有两种方法:

选项1:由应用层处理

这个方案实现起来很简单,只要让应用层调用import()即可。

// my-lib.jsclass MyLib {
   constructor
(options) {
       
this.onLoadDeps = options.onLoadDeps || null;
   
}

   loadDeps
() {
       
return new Promise((resolve, reject) => {
           
if (global.TextDecoder === undefined) {
               
if (this.onLoadDeps) {
                 
this.onLoadDeps().then(resolve).catch(reject);
               
} else {
                   
Promise.all([
                       
import('./deps/text-encoding'),
                       
import('./deps/other-dep.js'),
                       
import('./deps/another-dep.js'),
                   
]).then(resolve).catch(reject);
               
}
           
} else {
               resolve
();
           
}
       
});
   
}}// app.jsimport MyLib from 'my-lib';class App {
   constructor
() {
       
this.myLib = new MyLib({
           onLoadDeps
: () => Promise.all([
               
import('my-lib/dist/text-encoding'),
               
import('my-lib/dist/other-dep.js'),
               
import('my-lib/dist/another-dep.js'),
           
]);
       
});
   
}}

这些方法非常简单,但是不需要任何处理就可以在开发环境中正常运行。

缺点是,如果多个项目引用该例程,那么当解释器添加新的依赖项时webpack按需加载,所有引用该例程的项目都将不得不更改源代码。 这将是一件相当漫长的事情。

对于这个解决方案,虽然有一个变种,但是相对来说更加方便:

// my-lib.jsclass MyLib {
   constructor
(options) {
       
this.importPrefix = options.importPrefix || './deps';
   
}

   loadDeps
() {
       
return new Promise((resolve, reject) => {
           
if (global.TextDecoder === undefined) {
               
return Promise.all([
                   
import(this.importPrefix + '/text-encoding'),
                   
import(this.importPrefix + '/other-dep.js'),
                   
import(this.importPrefix + '/another-dep.js'),
               
]).then(resolve).catch(reject);
           
} else {
               resolve
();
           
}
       
});
   
}}// app.jsimport MyLib from 'my-lib';class App {
   constructor
() {
       
this.myLib = new MyLib({ importPrefix: 'my-lib/dist' });
   
}}

注意:此变体可能会报告 Critical dependency:requestofdependencyisanexpression 错误。

选项 2:由泛型处理

这个方案稍微复杂一些。

如果你想让webpack不处理import(),那么你就不能让webpack解析富含import()的文件,也就是说,你需要将富含加载依赖的部分分离到另一个文件中。

// runtime.jsmodule.exports = {
   onLoadDeps
: function() {
       
return Promise.all([
           
import('my-lib/dist/text-encoding'),
           
import('my-lib/dist/other-dep.js'),
           
import('my-lib/dist/another-dep.js'),
       
]);
   
}}

注意:由于 webpack 不会解析这个文件,加载器也不会处理这个文件,所以最好在这个文件上使用 Node.js 原生支持的句型。

// my-lib.jsimport RUNTIME from './runtime';class MyLib {
   loadDeps
() {
       
return new Promise((resolve, reject) => {
         
if (global.TextDecoder === undefined) {
              RUNTIME
.onLoadDeps().then(resolve).catch(reject);
         
} else {
              resolve
();
         
}
       
});
   
}}

之后更改 webpack 配置:

module.exports = {
   output
: { ... },
   
module: {
       noParse
: /src/runtime/,
   
},
   plugins
: [ ... ]
}

webpack 多个页面-webpack打包多个页面的方式

这样,webpack在处理my-lib.js时会加载runtime.js,但不会解析它。 所以你会得到以下结果:

// dist/index.js/******/ ([/* 0 *//***/ (function(module, exports) {module.exports = {
 onLoadDeps
: function onLoadDeps() {
   
return Promise.all([import('my-lib/dist/text-encoding'), import('my-lib/dist/other-dep.js'), import('my-lib/dist/another-dep.js')]);
 
}};/***/ }),/* 1 *//***/ (function(module, __webpack_exports__, __webpack_require__) {// ...var MyLib =/*#__PURE__*/function () {
 
function MyLib() {}

 
var _proto = MyLib.prototype;

 _proto
.loadDeps = function loadDeps() {
   
var _this = this;

   
return new Promise(function (resolve, reject) {
     
if (global.TextDecoder === undefined) {
       _runtime__WEBPACK_IMPORTED_MODULE_0___default
.a.onLoadDeps().then(resolve).catch(reject);
     
} else {
       resolve
();
     
}
   
});
 
};

 _proto
.initialize = function initialize() {
   
this.decoder = new TextDecoder(); // ...
 
};

 
return MyLib;}();// ...

如果应用层引用了这个例程,那么webpack在打包应用的时候就会处理例程中的import(),和平时应用层的动态加载是一样的,里面的问题就迎刃而解了。

最后剩下的问题是,在开发环境中,我们还需要测试runtime.js,但此时导入的是('my-lib/dist/xxx'),肯定会报 Error: Cannotfindmodule 错误。

这时可以像解决方案1那样通过import(importPrefix+'/text-encoding')的形式来解决,也可以借助NormalModuleReplacementPlugin来解决。

// webpack.dev.jsmodule.exports = {
   
// ...
   plugins
: [
       
new webpack.NormalModuleReplacementPlugin(/my-lib/dist/(.*)/, function (resource) {
           resource
.request = resource.request.replace(/my-lib/dist/, '../src/deps')
       
}),
   
]}

这个插件可以改变重定向的资源,里面的配置是将my-lib/dist/*上面的资源重定向到../src/deps。

更详细的使用方法请参考官方文档NormalModuleReplacementPlugin。

注意:此插件最好仅在开发环境中使用。

这个方案看起来有点冗长,但最大的好处是可以将依赖管理交给泛型本身,无需应用层干预。 即使以后泛型改变了打包位置(不再是dist/),也不需要让应用层知道。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 webpack webpack 多个页面-webpack打包多个页面的方式 https://www.wkzy.net/game/128247.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务