Category - 前端开发

如何在 webpack 中引入未模块化的库,如 Zepto

By go - 星期六, 四月 22, 2017

原文链接:https://sebastianblade.com/how-to-import-unmodular-library-like-zepto/最近我在研究多页面 webpack 模块打包的完整解决方案时,发现用 import 导入 Zepto 时,会报 Uncaught TypeError: Cannot read property ‘createElement’ of undefined 错误,导致无法愉快地使用 Zepto。在经过一番调试和搜索后终于找到了解决的办法,并且对于所有不支持模块化的库都可以用这种方法导入模块。 原因Zepto 的源码:/* Zepto v1.2.0 – zepto event ajax form ie – zeptojs.com/license */
(function(global, factory) {
if (typeof define === ‘function’ && define.amd)
define(function() { return factory(global) })
else
factory(global)
}(this, function(window) {
var Zepto = (function() {

// …

return $
})()

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

return Zepto
}))
可以看出,它只使用了 AMD 规范的模块导出方法 define,没有用 CommonJs 规范的方法 module.exports 来导出模块,不过这不是造成报错的原因。先来看一下 webpack 运行模块的方法: 再来看一下在 webpack config 中不作任何处理,直接 import $ from ‘zepto’,经过 webpack 转换的 Zepto 的模块闭包:以上代码是模块执行的闭包,化简一下其实就是 webpack 把 AMD 规范的 define 方法转换成了 module.export = factory(global),以此来获取 factory 方法返回的对象。在模块加载(import/require)时,webpack 会通过下面这种方法来执行模块闭包并导入模块:// The require function
function __webpack_require__(moduleId) {

// Check if module is in cache
if(installedModules[moduleId])
return installedModules[moduleId].exports;

// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
exports: {},
id: moduleId,
loaded: false,
hot: hotCreateModule(moduleId),
parents: hotCurrentParents,
children: []
}

// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));

// Flag the module as loaded
module.loaded = true

// Return the exports of the module
return module.exports
}
其核心在于 modules[moduleId].call,它会传入新初始化的 module.exports 来作为模块闭包的上下文(context),并运行模块闭包来将模块暴露的对象加入到已加载的模块对象(installedModules)中。所以对于 Zepto 来说,它初始化时使用的 this(见下图)其实就是 module.exports,但这个 module.exports 没有赋值过任何变量,即 Zepto 初始化使用的 this 为空对象。所以 factory(global) 中 global 为空对象,Zepto 运行函数中的 window 也就变成了空对象,而 var document = window.document,这个 document 为 undefined,因此会造成 document.createElement 会报 TypeError。解决方法$ npm i -D script-loader exports-loader
要用到两个 loader:exports-loader 和 script-loader。script-loaderrequire(“script!./zepto.js”);
// => execute file.js once in global context
script-loader 可以在我们 import/require 模块时,在全局上下文环境中运行一遍模块 JS 文件(不管 require 几次,模块仅运行一次)。script-loader 把我们指定的模块 JS 文件转成纯字符串,并用 eval.call(null, string) 执行,这样执行的作用域就为全局作用域了。但如果只用 script-loader,我们要导入 Zepto 对象就需要这么写:// entry.js
/*
* 不能使用 `import $ from ‘zepto’`
* 因为 zepto.js 执行后返回值为 undefined
* 因为 module.exports 默认初始为空对象
* 所以 $ 也为空对象
*/

$(function () { })
这样的写法就是:当 webpack 初始化(webpackBootstrap)时,zepto.js 会在全局作用域下执行一遍,将 Zepto 对象赋值给 window.$ 并挂载到 window 上。因此后续的 $、Zepto 变量就都可用了。不过这种持续依赖全局对象的实现方法不太科学,还是将对象以 ES6 Module/CommonJs/AMD 方式暴露出来更好。Note:如果我们用的库没有把对象挂载到全局的话,就没法作为模块使用了(还是趁早提个 PR 模块化一下吧)。exports-loader为了让我们的模块导入更加地「模块化」,可以 import/require,而不是像上面那么「与众不同」,我们还需要 exports-loader 的帮助。exports-loader 可以导出我们指定的对象:require(‘exports?window.Zepto!./zepto.js’)
他的作用就是在模块闭包最后加一句 module.exports = window.Zepto 来导出我们需要的对象,这样我们就可以愉快地 import $ from ‘zepto’ 了。webpack 配置废话说了那么多,终于可以告诉大家怎么直接使用这两个 loader 来「模块化」Zepto 了:// webpack.config
{
// …
module: {
loaders: [{
test: require.resolve(‘zepto’),
loader: ‘exports-loader?window.Zepto!script-loader’
}]
}
}
这样我们在页面入口文件中就可以这么写:// entry.js
import $ from ‘zepto’

$(function () {
// …
})
Note:require.resolve() 是 nodejs 用来查找模块位置的方法,返回模块的入口文件,如 zepto 为 ./node_modules/zepto/dist/zepto.js。其他如果你不想写 import $ from ‘zepto’,并且想用其他变量来代替 Zepto。可以使用官方的一个插件:webpack.ProvidePlugin。webpack.ProvidePlugin 是一个依赖注入类型的插件,可以让你在使用指定变量时,比如直接使用 $ 时,自动加载指定的模块 zepto,并将其暴露的对象赋值给 $:// webpack.config
{
// …
plugins: [
new webpack.ProvidePlugin({
$: ‘zepto’,
// 把 Zepto 导入为 abc 变量也可以
abc: ‘zepto’
})
// …
]
}
这样就可以直接使用赋值了 Zepto 对象的 $/abc 变量了~// entry.js
$(function () {
// …
abc(document)
})
如果不想这么麻烦地用两个 loader 来解决问题,可以把不支持模块化的库模块化,比如用这个 npm 包:webpack-zepto。但这个包已经一年多没更新了,所以我更推荐上面比较麻烦的做法来确保引入的模块是最新的。总结由于我们用 npm 下载的模块没有模块化,因此我们要使用:script-loader 全局上下文环境中执行模块 JS 文件; exports-loader 添加 module.exports 来主动暴露需要的对象,使其模块化。这样的方法适用于所有的库,不过最好的解决办法是从根本上让这些让这些库支持模块化。参考 webpack.ProvidePlugin with zeptoexports-loader · webpack指南

jQuery scrollFix滚动定位插件

By Lee - 星期二, 四月 4, 2017

【插件功能】当用户向上或向下滚动页面到一定位置时,目标元素开始固定定位(position:fixed),当回滚到原位置时目标元素恢复到原状态,可以定制触发滚动相对屏幕位置和触发滚动方向,兼容IE6【插件参数】$(“.target_element”).scrollFix( [ “top” | “bottom” | length(可以为负,表示相对bottom), [ “top” | “bottom” ] ]);第一个参数: 可选,默认为”top”,当目标元素到了屏幕相对的位置时开始触发固定,可以填一个数值,如100,-200 ,负值表示相对于屏幕下方第一个参数: 可选,默认为”top”,表示触发固定的滚动方向,”top”表示从上向下滚动时触发,”bottom”表示从下向上滚动时触发【下载插件】下载插件(download)【代码示例】$(“#a”).scrollFix(-200); 滚动到距离下面200px时开始固定,默认从上到下触发   $(“#b”).scrollFix(200,”bottom”); 滚动到距离上面200px时开始固定,指定”bottom”从下到上触发   $(“#c”).scrollFix(“top”,”top”); 滚动到距离上面0时开始固定,指定”top”从上到下触发   $(“#d”).scrollFix(“bottom”,”top”); 滚动到距离下面0时开始固定,指定”bottom”从下到上触发    ;(function($) {
jQuery.fn.scrollFix = function(height, dir) {
height = height || 0;
height = height == “top” ? 0 : height;
return this.each(function() {
if (height == “bottom”) {
height = document.documentElement.clientHeight – this.scrollHeight;
} else if (height < 0) {
height = document.documentElement.clientHeight – this.scrollHeight + height;
}
var that = $(this),
oldHeight = false,
p, r, l = that.offset().left;
dir = dir == “bottom” ? dir : “top”; //默认滚动方向向下
if (window.XMLHttpRequest) { //非ie6用fixed

function getHeight() { //>=0表示上面的滚动高度大于等于目标高度
return (document.documentElement.scrollTop || document.body.scrollTop) + height – that.offset().top;
}
$(window).scroll(function() {
if (oldHeight === false) {
if ((getHeight() >= 0 && dir == “top”) || (getHeight() <= 0 && dir == “bottom”)) {
oldHeight = that.offset().top – height;
that.css({
position: “fixed”,
top: height,
left: l
});
}
} else {
if (dir == “top” && (document.documentElement.scrollTop || document.body.scrollTop) < oldHeight) {
that.css({
position: “static”
});
oldHeight = false;
} else if (dir == “bottom” && (document.documentElement.scrollTop || document.body.scrollTop) > oldHeight) {
that.css({
position: “static”
});
oldHeight = false;
}
}
});
} else { //for ie6
$(window).scroll(function() {
if (oldHeight === false) { //恢复前只执行一次,减少reflow
if ((getHeight() >= 0 && dir == “top”) || (getHeight() <= 0 && dir == “bottom”)) {
oldHeight = that.offset().top – height;
r = document.createElement(“span”);
p = that[0].parentNode;
p.replaceChild(r, that[0]);
document.body.appendChild(that[0]);
that[0].style.position = “absolute”;
}
} else if ((dir == “top” && (document.documentElement.scrollTop || document.body.scrollTop) < oldHeight) || (dir == “bottom” && (document.documentElement.scrollTop || document.body.scrollTop) > oldHeight)) { //结束
that[0].style.position = “static”;
p.replaceChild(that[0], r);
r = null;
oldHeight = false;
} else { //滚动
that.css({
left: l,
top: height + document.documentElement.scrollTop
})
}
});
}
});
};
})(jQuery);
  

Webpack 常见静态资源处理 – 模块加载器(Loaders)+ExtractTextPlugin插件

By Lee - 星期二, 四月 4, 2017

Webpack 常见静态资源处理 – 模块加载器(Loaders)+ExtractTextPlugin插件webpack系列目录webpack 系列 一:模块系统的演进webpack 系列 二:webpack 介绍&安装webpack 系列 三:webpack 如何集成第三方js库webpack 系列 四:webpack 多页面支持 & 公共组件单独打包webpack 系列 五:webpack Loaders 模块加载器webpack 系列 六:前端项目模板-webpack+gulp实现自动构建部署基于webpack搭建纯静态页面型前端工程解决方案模板, 最终形态源码见github: https://github.com/ifengkou/webpack-template正文Webpack将所有静态资源都认为是模块,比如JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等等,从而可以对其进行统一管理。为此Webpack引入了加载器的概念,除了纯JavaScript之外,每一种资源都可以通过对应的加载器处理成模块。和大多数包管理器不一样的是,Webpack的加载器之间可以进行串联,一个加载器的输出可以成为另一个加载器的输入。比如LESS文件先通过less-load处理成css,然后再通过css-loader加载成css模块,最后由style-loader加载器对其做最后的处理,从而运行时可以通过style标签将其应用到最终的浏览器环境。一 常用loader安装css/sass/less loader加载器cnpm install file-loader css-loader style-loader sass-loader ejs-loader html-loader jsx-loader image-webpack-loader –save-devwebpack.config.js配置:module: {
loaders: [
{
test: /.((woff2?|svg)(?v=[0-9].[0-9].[0-9]))|(woff2?|svg|jpe?g|png|gif|ico)$/,
loaders: [
// 小于10KB的图片会自动转成dataUrl
‘url?limit=10240&name=img/[hash:8].[name].[ext]’,
‘image?{bypassOnDebug:true, progressive:true,optimizationLevel:3,pngquant:{quality:”65-80″,speed:4}}’
]
},
{
test: /.((ttf|eot)(?v=[0-9].[0-9].[0-9]))|(ttf|eot)$/,
loader: ‘url?limit=10000&name=fonts/[hash:8].[name].[ext]’
},
{test: /.(tpl|ejs)$/, loader: ‘ejs’},
{test: /.css$/, loader: ‘style-loader!css-loader’},
{ test: /.scss$/, loader: ‘style!css!sass’}
]
},index.html 新增两个div<div class=”small-webpack”></div>
<div class=”webpack”></div>index.css 增加两个图片,同时将webpack.png(53kb) 和 small-webpack.png(9.8k).webpack {
background: url(../img/webpack.png) no-repeat center;
height:500px;
}
.small-webpack {
background: url(../img/small-webpack.png) no-repeat center;
height:250px;
}index.js 引入cssrequire(‘../css/index.css’);执行webpack指令$ webpack查看生成的目录结构其中并没有css文件,css被写入到了index.js中,index.js 部分截图总结:图片采用了url-loader加载,如果小于10kb,图片则被转化成 base64 格式的 dataUrl css文件被打包进了js文件中css被打包进了js文件,如果接受不了,可以强制把css从js文件中独立出来。官方文档是以插件形式实现:文档docs点这,插件的github点这二:extract-text-webpack-plugin 插件介绍Extract text from bundle into a file.从bundle中提取出特定的text到一个文件中。使用 extract-text-webpack-plugin就可以把css从js中独立抽离出来安装$ npm install extract-text-webpack-plugin –save-dev使用(css为例)var ExtractTextPlugin = require(“extract-text-webpack-plugin”);
module.exports = {
module: {
loaders: [
{ test: /.css$/, loader: ExtractTextPlugin.extract(“style-loader”, “css-loader”) }
]
},
plugins: [
new ExtractTextPlugin(“styles.css”)
]
}它将从每一个用到了require(“style.css”)的entry chunks文件中抽离出css到单独的output文件APInew ExtractTextPlugin([id: string], filename: string, [options])id Unique ident for this plugin instance. (For advanded usage only, by default automatic generated) filename the filename of the result file. May contain [name], [id] and [contenthash]. [name] the name of the chunk [id] the number of the chunk [contenthash] a hash of the content of the extracted file optionsallChunks extract from all additional chunks too (by default it extracts only from the initial chunk(s)) disable disables the plugin ExtractTextPlugin.extract([notExtractLoader], loader, [options])根据已有的loader,创建一个提取器(loader的再封装)notExtractLoader (可选)当css没有被抽离时,加载器不应该使用(例如:当allChunks:false时,在一个additional 的chunk中) loader 数组,用来转换css资源的加载器s optionspublicPath 重写该加载器(loader)的 publicPath 的设置多入口文件的extract的使用示例:let ExtractTextPlugin = require(‘extract-text-webpack-plugin’);

// multiple extract instances
let extractCSS = new ExtractTextPlugin(‘stylesheets/[name].css’);
let extractLESS = new ExtractTextPlugin(‘stylesheets/[name].less’);

module.exports = {

module: {
loaders: [
{test: /.scss$/i, loader: extractCSS.extract([‘css’,’sass’])},
{test: /.less$/i, loader: extractLESS.extract([‘css’,’less’])},

]
},
plugins: [
extractCSS,
extractLESS
]
};三:改造项目-抽离css安装插件到项目npm install extract-text-webpack-plugin –save-dev配置webpack.config.js,加入ExtractTextPlugin和相关处理:var webpack = require(“webpack”);
var path = require(“path”);
var srcDir = path.resolve(process.cwd(), ‘src’);
var nodeModPath = path.resolve(__dirname, ‘./node_modules’);
var pathMap = require(‘./src/pathmap.json’);
var glob = require(‘glob’)
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
var HtmlWebpackPlugin = require(‘html-webpack-plugin’);
var ExtractTextPlugin = require(‘extract-text-webpack-plugin’);
var entries = function () {
var jsDir = path.resolve(srcDir, ‘js’)
var entryFiles = glob.sync(jsDir + ‘/*.{js,jsx}’)
var map = {};

for (var i = 0; i < entryFiles.length; i++) {
var filePath = entryFiles[i];
var filename = filePath.substring(filePath.lastIndexOf(‘/’) + 1, filePath.lastIndexOf(‘.’));
map[filename] = filePath;
}
return map;
}

var html_plugins = function () {
var entryHtml = glob.sync(srcDir + ‘/*.html’)
var r = []
var entriesFiles = entries()
for (var i = 0; i < entryHtml.length; i++) {
var filePath = entryHtml[i];
var filename = filePath.substring(filePath.lastIndexOf(‘/’) + 1, filePath.lastIndexOf(‘.’));
var conf = {
template: ‘html!’ + filePath,
filename: filename + ‘.html’
}
//如果和入口js文件同名
if (filename in entriesFiles) {
conf.inject = ‘body’
conf.chunks = [‘vendor’, filename]
}
//跨页面引用,如pageA,pageB 共同引用了common-a-b.js,那么可以在这单独处理
//if(pageA|pageB.test(filename)) conf.chunks.splice(1,0,’common-a-b’)
r.push(new HtmlWebpackPlugin(conf))
}
return r
}
var plugins = [];
var extractCSS = new ExtractTextPlugin(‘css/[name].css?[contenthash]’)
var cssLoader = extractCSS.extract([‘css’])
var sassLoader = extractCSS.extract([‘css’, ‘sass’])

plugins.push(extractCSS);

plugins.push(new CommonsChunkPlugin({
name: ‘vendor’,
minChunks: Infinity
}));

module.exports = {
entry: Object.assign(entries(), {
// 用到什么公共lib(例如jquery.js),就把它加进vendor去,目的是将公用库单独提取打包
‘vendor’: [‘jquery’, ‘avalon’]
}),
output: {
path: path.join(__dirname, “dist”),
filename: “[name].js”,
chunkFilename: ‘[chunkhash:8].chunk.js’,
publicPath: “/”
},
module: {
loaders: [
{
test: /.((woff2?|svg)(?v=[0-9].[0-9].[0-9]))|(woff2?|svg|jpe?g|png|gif|ico)$/,
loaders: [
//小于10KB的图片会自动转成dataUrl,
‘url?limit=10000&name=img/[hash:8].[name].[ext]’,
‘image?{bypassOnDebug:true, progressive:true,optimizationLevel:3,pngquant:{quality:”65-80″,speed:4}}’
]
},
{
test: /.((ttf|eot)(?v=[0-9].[0-9].[0-9]))|(ttf|eot)$/,
loader: ‘url?limit=10000&name=fonts/[hash:8].[name].[ext]’
},
{test: /.(tpl|ejs)$/, loader: ‘ejs’},
{test: /.css$/, loader: cssLoader},
{test: /.scss$/, loader: sassLoader}
]
},
resolve: {
extensions: [”, ‘.js’, ‘.css’, ‘.scss’, ‘.tpl’, ‘.png’, ‘.jpg’],
root: [srcDir, nodeModPath],
alias: pathMap,
publicPath: ‘/’
},
plugins: plugins.concat(html_plugins())
}其中,用ExtractTextPlugin 来抽离cssvar ExtractTextPlugin = require(‘extract-text-webpack-plugin’);
var extractCSS = new ExtractTextPlugin(‘css/[name].css?[contenthash]’)
var cssLoader = extractCSS.extract([‘css’])
var sassLoader = extractCSS.extract([‘css’, ‘sass’])

plugins.push(extractCSS);
……
//conf – module – loaders
{test: /.css$/, loader: cssLoader},
{test: /.scss$/, loader: sassLoader}注意事项:css中img的路径会出现问题,通过设置publicPath 解决,采用绝对路径output: {
……
publicPath: “/”
},运行:$ webpack期望css单独抽离,打包成单独的css文件 html自动引用css文件 小于10k的图片,转成base64 格式的 dataUrl webpack.png 会被压缩,减少文件大小运行webpack后的项目的目录结构:生成的 dist/index.html 自动引用了 index.css 和相关的js,由于设置了publicPath 所以相应的链接都采用了绝对路径生成的 dist/index.css 小图片被转成了data:image形式:结果:css单独打包到css目录 html自动注入了link 标签 small-webpack.png 小于10k,被打包进了index.css webpack.png 由原来的50+k 被压缩成 10- k最后,运行 webpack-dev-server 看一下运行结果:总结Webpack将所有静态资源都认为是模块,而通过loader,几乎可以处理所有的静态资源,图片、css、sass之类的。并且通过一些插件如extract-text-webpack-plugin,可以将共用的css抽离出来下篇介绍改进webpack.config.js:区分开发环境和生产环境 集成 gulp 实现自动构建打包部署 github 发布 前端自动化构建的项目模板

webpack打包后使用jQuery的问题

By Lee - 星期二, 四月 4, 2017

使用webpack打包了页面上使用的一些js文件
配置文件:var webpack = require(“webpack”);
var path= require(“path”);
module.exports = {
entry: [
“./admin/public/entry/entry.js”
],
output: {
path: path.join(__dirname,”/admin/public/js/out”),
filename:”bundle.js”
},
module: {
loaders: [{
test: /.css$/,
loader:”style!css”
}]
},
plugins: [
new webpack.ProvidePlugin({
$:”jquery”,
jQuery:”jquery”,
“window.jQuery”:”jquery”
})
],
}入口文件:require(‘bootstrap’);
require(‘bootstrap-switch’);
require(‘bootstrap-timepicker’);
require(‘../js/jquery.cookies.js’);
require(‘gritter’);
require(‘../js/datatable.js’);
require(‘../js/custom.js’);
require(‘../js/common.js’);
require(‘../js/tag.js’);
require(‘jquery-ui’);
require(‘cropper’);
require(‘ztree’);打包完之后再网页上使用jQuery会报 Uncaught ReferenceError: $ is not defined 表示理解不能,为何在打包的js插件中使用jq没问题,在网页中却要报错

Javascript中的去掉空行

By Lee - 星期二, 二月 11, 2014

几个正则表式的应用无论在Java还是Javascript中对正则表达式都给了非常高效的实现。能够用好正则表达式 […]